Index: head/sys/dev/acpi_support/acpi_asus.c =================================================================== --- head/sys/dev/acpi_support/acpi_asus.c (revision 339753) +++ head/sys/dev/acpi_support/acpi_asus.c (revision 339754) @@ -1,1285 +1,1286 @@ /*- * Copyright (c) 2004, 2005 Philip Paeps * 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. */ #include __FBSDID("$FreeBSD$"); /* * Driver for extra ACPI-controlled gadgets (hotkeys, leds, etc) found on * recent Asus (and Medion) laptops. Inspired by the acpi4asus project which * implements these features in the Linux kernel. * * * * Currently should support most features, but could use some more testing. * Particularly the display-switching stuff is a bit hairy. If you have an * Asus laptop which doesn't appear to be supported, or strange things happen * when using this driver, please report to . */ #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include /* Methods */ #define ACPI_ASUS_METHOD_BRN 1 #define ACPI_ASUS_METHOD_DISP 2 #define ACPI_ASUS_METHOD_LCD 3 #define ACPI_ASUS_METHOD_CAMERA 4 #define ACPI_ASUS_METHOD_CARDRD 5 #define ACPI_ASUS_METHOD_WLAN 6 #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("ASUS") struct acpi_asus_model { char *name; char *bled_set; char *dled_set; char *gled_set; char *mled_set; char *tled_set; char *wled_set; char *brn_get; char *brn_set; char *brn_up; char *brn_dn; char *lcd_get; char *lcd_set; char *disp_get; char *disp_set; char *cam_get; char *cam_set; char *crd_get; char *crd_set; char *wlan_get; char *wlan_set; void (*n_func)(ACPI_HANDLE, UINT32, void *); char *lcdd; void (*lcdd_n_func)(ACPI_HANDLE, UINT32, void *); }; struct acpi_asus_led { struct acpi_asus_softc *sc; struct cdev *cdev; int busy; int state; enum { ACPI_ASUS_LED_BLED, ACPI_ASUS_LED_DLED, ACPI_ASUS_LED_GLED, ACPI_ASUS_LED_MLED, ACPI_ASUS_LED_TLED, ACPI_ASUS_LED_WLED, } type; }; struct acpi_asus_softc { device_t dev; ACPI_HANDLE handle; ACPI_HANDLE lcdd_handle; struct acpi_asus_model *model; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; struct acpi_asus_led s_bled; struct acpi_asus_led s_dled; struct acpi_asus_led s_gled; struct acpi_asus_led s_mled; struct acpi_asus_led s_tled; struct acpi_asus_led s_wled; int s_brn; int s_disp; int s_lcd; int s_cam; int s_crd; int s_wlan; }; static void acpi_asus_lcdd_notify(ACPI_HANDLE h, UINT32 notify, void *context); /* * We can identify Asus laptops from the string they return * as a result of calling the ATK0100 'INIT' method. */ static struct acpi_asus_model acpi_asus_models[] = { { .name = "xxN", .mled_set = "MLED", .wled_set = "WLED", .lcd_get = "\\BKLT", .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\ADVG", .disp_set = "SDSP" }, { .name = "A1x", .mled_set = "MLED", .lcd_get = "\\BKLI", .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10", .brn_up = "\\_SB.PCI0.ISA.EC0._Q0E", .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0F" }, { .name = "A2x", .mled_set = "MLED", .wled_set = "WLED", .lcd_get = "\\BAOF", .lcd_set = "\\Q10", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\INFB", .disp_set = "SDSP" }, { .name = "A3E", .mled_set = "MLED", .wled_set = "WLED", .lcd_get = "\\_SB.PCI0.SBRG.EC0.RPIN(0x67)", .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\_SB.PCI0.P0P2.VGA.GETD", .disp_set = "SDSP" }, { .name = "A3F", .mled_set = "MLED", .wled_set = "WLED", .bled_set = "BLED", .lcd_get = "\\_SB.PCI0.SBRG.EC0.RPIN(0x11)", .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\SSTE", .disp_set = "SDSP" }, { .name = "A3N", .mled_set = "MLED", .bled_set = "BLED", .wled_set = "WLED", .lcd_get = "\\BKLT", .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD", .disp_set = "SDSP" }, { .name = "A4D", .mled_set = "MLED", .brn_up = "\\_SB_.PCI0.SBRG.EC0._Q0E", .brn_dn = "\\_SB_.PCI0.SBRG.EC0._Q0F", .brn_get = "GPLV", .brn_set = "SPLV", #ifdef notyet .disp_get = "\\_SB_.PCI0.SBRG.EC0._Q10", .disp_set = "\\_SB_.PCI0.SBRG.EC0._Q11" #endif }, { .name = "A6V", .bled_set = "BLED", .mled_set = "MLED", .wled_set = "WLED", .lcd_get = NULL, .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\_SB.PCI0.P0P3.VGA.GETD", .disp_set = "SDSP" }, { .name = "A8SR", .bled_set = "BLED", .mled_set = "MLED", .wled_set = "WLED", .lcd_get = NULL, .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", .disp_set = "SDSP", .lcdd = "\\_SB.PCI0.P0P1.VGA.LCDD", .lcdd_n_func = acpi_asus_lcdd_notify }, { .name = "D1x", .mled_set = "MLED", .lcd_get = "\\GP11", .lcd_set = "\\Q0D", .brn_up = "\\Q0C", .brn_dn = "\\Q0B", .disp_get = "\\INFB", .disp_set = "SDSP" }, { .name = "G2K", .bled_set = "BLED", .dled_set = "DLED", .gled_set = "GLED", .mled_set = "MLED", .tled_set = "TLED", .wled_set = "WLED", .brn_get = "GPLV", .brn_set = "SPLV", .lcd_get = "GBTL", .lcd_set = "SBTL", .disp_get = "\\_SB.PCI0.PCE2.VGA.GETD", .disp_set = "SDSP", }, { .name = "L2D", .mled_set = "MLED", .wled_set = "WLED", .brn_up = "\\Q0E", .brn_dn = "\\Q0F", .lcd_get = "\\SGP0", .lcd_set = "\\Q10" }, { .name = "L3C", .mled_set = "MLED", .wled_set = "WLED", .brn_get = "GPLV", .brn_set = "SPLV", .lcd_get = "\\GL32", .lcd_set = "\\_SB.PCI0.PX40.ECD0._Q10" }, { .name = "L3D", .mled_set = "MLED", .wled_set = "WLED", .brn_get = "GPLV", .brn_set = "SPLV", .lcd_get = "\\BKLG", .lcd_set = "\\Q10" }, { .name = "L3H", .mled_set = "MLED", .wled_set = "WLED", .brn_get = "GPLV", .brn_set = "SPLV", .lcd_get = "\\_SB.PCI0.PM.PBC", .lcd_set = "EHK", .disp_get = "\\_SB.INFB", .disp_set = "SDSP" }, { .name = "L4R", .mled_set = "MLED", .wled_set = "WLED", .brn_get = "GPLV", .brn_set = "SPLV", .lcd_get = "\\_SB.PCI0.SBSM.SEO4", .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", .disp_set = "SDSP" }, { .name = "L5x", .mled_set = "MLED", .tled_set = "TLED", .lcd_get = "\\BAOF", .lcd_set = "\\Q0D", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\INFB", .disp_set = "SDSP" }, { .name = "L8L" /* Only has hotkeys, apparently */ }, { .name = "M1A", .mled_set = "MLED", .brn_up = "\\_SB.PCI0.PX40.EC0.Q0E", .brn_dn = "\\_SB.PCI0.PX40.EC0.Q0F", .lcd_get = "\\PNOF", .lcd_set = "\\_SB.PCI0.PX40.EC0.Q10" }, { .name = "M2E", .mled_set = "MLED", .wled_set = "WLED", .brn_get = "GPLV", .brn_set = "SPLV", .lcd_get = "\\GP06", .lcd_set = "\\Q10" }, { .name = "M6N", .mled_set = "MLED", .wled_set = "WLED", .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .lcd_get = "\\_SB.BKLT", .brn_set = "SPLV", .brn_get = "GPLV", .disp_set = "SDSP", .disp_get = "\\SSTE" }, { .name = "M6R", .mled_set = "MLED", .wled_set = "WLED", .brn_get = "GPLV", .brn_set = "SPLV", .lcd_get = "\\_SB.PCI0.SBSM.SEO4", .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .disp_get = "\\SSTE", .disp_set = "SDSP" }, { .name = "S1x", .mled_set = "MLED", .wled_set = "WLED", .lcd_get = "\\PNOF", .lcd_set = "\\_SB.PCI0.PX40.Q10", .brn_get = "GPLV", .brn_set = "SPLV" }, { .name = "S2x", .mled_set = "MLED", .lcd_get = "\\BKLI", .lcd_set = "\\_SB.PCI0.ISA.EC0._Q10", .brn_up = "\\_SB.PCI0.ISA.EC0._Q0B", .brn_dn = "\\_SB.PCI0.ISA.EC0._Q0A" }, { .name = "V6V", .bled_set = "BLED", .tled_set = "TLED", .wled_set = "WLED", .lcd_get = "\\BKLT", .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\_SB.PCI0.P0P1.VGA.GETD", .disp_set = "SDSP" }, { .name = "W5A", .bled_set = "BLED", .lcd_get = "\\BKLT", .lcd_set = "\\_SB.PCI0.SBRG.EC0._Q10", .brn_get = "GPLV", .brn_set = "SPLV", .disp_get = "\\_SB.PCI0.P0P2.VGA.GETD", .disp_set = "SDSP" }, { .name = NULL } }; /* * Samsung P30/P35 laptops have an Asus ATK0100 gadget interface, * but they can't be probed quite the same way as Asus laptops. */ static struct acpi_asus_model acpi_samsung_models[] = { { .name = "P30", .wled_set = "WLED", .brn_up = "\\_SB.PCI0.LPCB.EC0._Q68", .brn_dn = "\\_SB.PCI0.LPCB.EC0._Q69", .lcd_get = "\\BKLT", .lcd_set = "\\_SB.PCI0.LPCB.EC0._Q0E" }, { .name = NULL } }; static void acpi_asus_eeepc_notify(ACPI_HANDLE h, UINT32 notify, void *context); /* * EeePC have an Asus ASUS010 gadget interface, * but they can't be probed quite the same way as Asus laptops. */ static struct acpi_asus_model acpi_eeepc_models[] = { { .name = "EEE", .brn_get = "\\_SB.ATKD.PBLG", .brn_set = "\\_SB.ATKD.PBLS", .cam_get = "\\_SB.ATKD.CAMG", .cam_set = "\\_SB.ATKD.CAMS", .crd_set = "\\_SB.ATKD.CRDS", .crd_get = "\\_SB.ATKD.CRDG", .wlan_get = "\\_SB.ATKD.WLDG", .wlan_set = "\\_SB.ATKD.WLDS", .n_func = acpi_asus_eeepc_notify }, { .name = NULL } }; static struct { char *name; char *description; int method; int flag_anybody; } acpi_asus_sysctls[] = { { .name = "lcd_backlight", .method = ACPI_ASUS_METHOD_LCD, .description = "state of the lcd backlight", .flag_anybody = 1 }, { .name = "lcd_brightness", .method = ACPI_ASUS_METHOD_BRN, .description = "brightness of the lcd panel", .flag_anybody = 1 }, { .name = "video_output", .method = ACPI_ASUS_METHOD_DISP, .description = "display output state", }, { .name = "camera", .method = ACPI_ASUS_METHOD_CAMERA, .description = "internal camera state", }, { .name = "cardreader", .method = ACPI_ASUS_METHOD_CARDRD, .description = "internal card reader state", }, { .name = "wlan", .method = ACPI_ASUS_METHOD_WLAN, .description = "wireless lan state", }, { .name = NULL } }; ACPI_SERIAL_DECL(asus, "ACPI ASUS extras"); /* Function prototypes */ static int acpi_asus_probe(device_t dev); static int acpi_asus_attach(device_t dev); static int acpi_asus_detach(device_t dev); static void acpi_asus_led(struct acpi_asus_led *led, int state); static void acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused); static int acpi_asus_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method); static int acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method); static int acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int val); static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context); static device_method_t acpi_asus_methods[] = { DEVMETHOD(device_probe, acpi_asus_probe), DEVMETHOD(device_attach, acpi_asus_attach), DEVMETHOD(device_detach, acpi_asus_detach), { 0, 0 } }; static driver_t acpi_asus_driver = { "acpi_asus", acpi_asus_methods, sizeof(struct acpi_asus_softc) }; static devclass_t acpi_asus_devclass; DRIVER_MODULE(acpi_asus, acpi, acpi_asus_driver, acpi_asus_devclass, 0, 0); MODULE_DEPEND(acpi_asus, acpi, 1, 1, 1); static int acpi_asus_probe(device_t dev) { struct acpi_asus_model *model; struct acpi_asus_softc *sc; struct sbuf *sb; ACPI_BUFFER Buf; ACPI_OBJECT Arg, *Obj; ACPI_OBJECT_LIST Args; static char *asus_ids[] = { "ATK0100", "ASUS010", NULL }; + int rv; char *rstr; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (acpi_disabled("asus")) return (ENXIO); - rstr = ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids); - if (rstr == NULL) { - return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, asus_ids, &rstr); + if (rv > 0) { + return (rv); } sc = device_get_softc(dev); sc->dev = dev; sc->handle = acpi_get_handle(dev); Arg.Type = ACPI_TYPE_INTEGER; Arg.Integer.Value = 0; Args.Count = 1; Args.Pointer = &Arg; Buf.Pointer = NULL; Buf.Length = ACPI_ALLOCATE_BUFFER; AcpiEvaluateObject(sc->handle, "INIT", &Args, &Buf); Obj = Buf.Pointer; /* * The Samsung P30 returns a null-pointer from INIT, we * can identify it from the 'ODEM' string in the DSDT. */ if (Obj->String.Pointer == NULL) { ACPI_STATUS status; ACPI_TABLE_HEADER th; status = AcpiGetTableHeader(ACPI_SIG_DSDT, 0, &th); if (ACPI_FAILURE(status)) { device_printf(dev, "Unsupported (Samsung?) laptop\n"); AcpiOsFree(Buf.Pointer); return (ENXIO); } if (strncmp("ODEM", th.OemTableId, 4) == 0) { sc->model = &acpi_samsung_models[0]; device_set_desc(dev, "Samsung P30 Laptop Extras"); AcpiOsFree(Buf.Pointer); - return (0); + return (rv); } /* EeePC */ if (strncmp("ASUS010", rstr, 7) == 0) { sc->model = &acpi_eeepc_models[0]; device_set_desc(dev, "ASUS EeePC"); AcpiOsFree(Buf.Pointer); - return (0); + return (rv); } } sb = sbuf_new_auto(); if (sb == NULL) return (ENOMEM); /* * Asus laptops are simply identified by name, easy! */ for (model = acpi_asus_models; model->name != NULL; model++) { if (strncmp(Obj->String.Pointer, model->name, 3) == 0) { good: sbuf_printf(sb, "Asus %s Laptop Extras", Obj->String.Pointer); sbuf_finish(sb); sc->model = model; device_set_desc_copy(dev, sbuf_data(sb)); sbuf_delete(sb); AcpiOsFree(Buf.Pointer); - return (0); + return (rv); } /* * Some models look exactly the same as other models, but have * their own ids. If we spot these, set them up with the same * details as the models they're like, possibly dealing with * small differences. * * XXX: there must be a prettier way to do this! */ else if (strncmp(model->name, "xxN", 3) == 0 && (strncmp(Obj->String.Pointer, "M3N", 3) == 0 || strncmp(Obj->String.Pointer, "S1N", 3) == 0)) goto good; else if (strncmp(model->name, "A1x", 3) == 0 && strncmp(Obj->String.Pointer, "A1", 2) == 0) goto good; else if (strncmp(model->name, "A2x", 3) == 0 && strncmp(Obj->String.Pointer, "A2", 2) == 0) goto good; else if (strncmp(model->name, "A3F", 3) == 0 && strncmp(Obj->String.Pointer, "A6F", 3) == 0) goto good; else if (strncmp(model->name, "D1x", 3) == 0 && strncmp(Obj->String.Pointer, "D1", 2) == 0) goto good; else if (strncmp(model->name, "L3H", 3) == 0 && strncmp(Obj->String.Pointer, "L2E", 3) == 0) goto good; else if (strncmp(model->name, "L5x", 3) == 0 && strncmp(Obj->String.Pointer, "L5", 2) == 0) goto good; else if (strncmp(model->name, "M2E", 3) == 0 && (strncmp(Obj->String.Pointer, "M2", 2) == 0 || strncmp(Obj->String.Pointer, "L4E", 3) == 0)) goto good; else if (strncmp(model->name, "S1x", 3) == 0 && (strncmp(Obj->String.Pointer, "L8", 2) == 0 || strncmp(Obj->String.Pointer, "S1", 2) == 0)) goto good; else if (strncmp(model->name, "S2x", 3) == 0 && (strncmp(Obj->String.Pointer, "J1", 2) == 0 || strncmp(Obj->String.Pointer, "S2", 2) == 0)) goto good; /* L2B is like L3C but has no lcd_get method */ else if (strncmp(model->name, "L3C", 3) == 0 && strncmp(Obj->String.Pointer, "L2B", 3) == 0) { model->lcd_get = NULL; goto good; } /* A3G is like M6R but with a different lcd_get method */ else if (strncmp(model->name, "M6R", 3) == 0 && strncmp(Obj->String.Pointer, "A3G", 3) == 0) { model->lcd_get = "\\BLFG"; goto good; } /* M2N and W1N are like xxN with added WLED */ else if (strncmp(model->name, "xxN", 3) == 0 && (strncmp(Obj->String.Pointer, "M2N", 3) == 0 || strncmp(Obj->String.Pointer, "W1N", 3) == 0)) { model->wled_set = "WLED"; goto good; } /* M5N and S5N are like xxN without MLED */ else if (strncmp(model->name, "xxN", 3) == 0 && (strncmp(Obj->String.Pointer, "M5N", 3) == 0 || strncmp(Obj->String.Pointer, "S5N", 3) == 0)) { model->mled_set = NULL; goto good; } } sbuf_printf(sb, "Unsupported Asus laptop: %s\n", Obj->String.Pointer); sbuf_finish(sb); device_printf(dev, "%s", sbuf_data(sb)); sbuf_delete(sb); AcpiOsFree(Buf.Pointer); return (ENXIO); } static int acpi_asus_attach(device_t dev) { struct acpi_asus_softc *sc; struct acpi_softc *acpi_sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); acpi_sc = acpi_device_get_parent_softc(dev); /* Build sysctl tree */ sysctl_ctx_init(&sc->sysctl_ctx); sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "asus", CTLFLAG_RD, 0, ""); /* Hook up nodes */ for (int i = 0; acpi_asus_sysctls[i].name != NULL; i++) { if (!acpi_asus_sysctl_init(sc, acpi_asus_sysctls[i].method)) continue; if (acpi_asus_sysctls[i].flag_anybody != 0) { SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_asus_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, i, acpi_asus_sysctl, "I", acpi_asus_sysctls[i].description); } else { SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_asus_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RW, sc, i, acpi_asus_sysctl, "I", acpi_asus_sysctls[i].description); } } /* Attach leds */ if (sc->model->bled_set) { sc->s_bled.busy = 0; sc->s_bled.sc = sc; sc->s_bled.type = ACPI_ASUS_LED_BLED; sc->s_bled.cdev = led_create_state((led_t *)acpi_asus_led, &sc->s_bled, "bled", 1); } if (sc->model->dled_set) { sc->s_dled.busy = 0; sc->s_dled.sc = sc; sc->s_dled.type = ACPI_ASUS_LED_DLED; sc->s_dled.cdev = led_create((led_t *)acpi_asus_led, &sc->s_dled, "dled"); } if (sc->model->gled_set) { sc->s_gled.busy = 0; sc->s_gled.sc = sc; sc->s_gled.type = ACPI_ASUS_LED_GLED; sc->s_gled.cdev = led_create((led_t *)acpi_asus_led, &sc->s_gled, "gled"); } if (sc->model->mled_set) { sc->s_mled.busy = 0; sc->s_mled.sc = sc; sc->s_mled.type = ACPI_ASUS_LED_MLED; sc->s_mled.cdev = led_create((led_t *)acpi_asus_led, &sc->s_mled, "mled"); } if (sc->model->tled_set) { sc->s_tled.busy = 0; sc->s_tled.sc = sc; sc->s_tled.type = ACPI_ASUS_LED_TLED; sc->s_tled.cdev = led_create_state((led_t *)acpi_asus_led, &sc->s_tled, "tled", 1); } if (sc->model->wled_set) { sc->s_wled.busy = 0; sc->s_wled.sc = sc; sc->s_wled.type = ACPI_ASUS_LED_WLED; sc->s_wled.cdev = led_create_state((led_t *)acpi_asus_led, &sc->s_wled, "wled", 1); } /* Activate hotkeys */ AcpiEvaluateObject(sc->handle, "BSTS", NULL, NULL); /* Handle notifies */ if (sc->model->n_func == NULL) sc->model->n_func = acpi_asus_notify; AcpiInstallNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, sc->model->n_func, dev); /* Find and hook the 'LCDD' object */ if (sc->model->lcdd != NULL && sc->model->lcdd_n_func != NULL) { ACPI_STATUS res; sc->lcdd_handle = NULL; res = AcpiGetHandle((sc->model->lcdd[0] == '\\' ? NULL : sc->handle), sc->model->lcdd, &(sc->lcdd_handle)); if (ACPI_SUCCESS(res)) { AcpiInstallNotifyHandler((sc->lcdd_handle), ACPI_DEVICE_NOTIFY, sc->model->lcdd_n_func, dev); } else { printf("%s: unable to find LCD device '%s'\n", __func__, sc->model->lcdd); } } return (0); } static int acpi_asus_detach(device_t dev) { struct acpi_asus_softc *sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); /* Turn the lights off */ if (sc->model->bled_set) led_destroy(sc->s_bled.cdev); if (sc->model->dled_set) led_destroy(sc->s_dled.cdev); if (sc->model->gled_set) led_destroy(sc->s_gled.cdev); if (sc->model->mled_set) led_destroy(sc->s_mled.cdev); if (sc->model->tled_set) led_destroy(sc->s_tled.cdev); if (sc->model->wled_set) led_destroy(sc->s_wled.cdev); /* Remove notify handler */ AcpiRemoveNotifyHandler(sc->handle, ACPI_SYSTEM_NOTIFY, acpi_asus_notify); if (sc->lcdd_handle) { KASSERT(sc->model->lcdd_n_func != NULL, ("model->lcdd_n_func is NULL, but lcdd_handle is non-zero")); AcpiRemoveNotifyHandler((sc->lcdd_handle), ACPI_DEVICE_NOTIFY, sc->model->lcdd_n_func); } /* Free sysctl tree */ sysctl_ctx_free(&sc->sysctl_ctx); return (0); } static void acpi_asus_led_task(struct acpi_asus_led *led, int pending __unused) { struct acpi_asus_softc *sc; char *method; int state; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = led->sc; switch (led->type) { case ACPI_ASUS_LED_BLED: method = sc->model->bled_set; state = led->state; break; case ACPI_ASUS_LED_DLED: method = sc->model->dled_set; state = led->state; break; case ACPI_ASUS_LED_GLED: method = sc->model->gled_set; state = led->state + 1; /* 1: off, 2: on */ break; case ACPI_ASUS_LED_MLED: method = sc->model->mled_set; state = !led->state; /* inverted */ break; case ACPI_ASUS_LED_TLED: method = sc->model->tled_set; state = led->state; break; case ACPI_ASUS_LED_WLED: method = sc->model->wled_set; state = led->state; break; default: printf("acpi_asus_led: invalid LED type %d\n", (int)led->type); return; } acpi_SetInteger(sc->handle, method, state); led->busy = 0; } static void acpi_asus_led(struct acpi_asus_led *led, int state) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (led->busy) return; led->busy = 1; led->state = state; AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)acpi_asus_led_task, led); } static int acpi_asus_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_asus_softc *sc; int arg; int error = 0; int function; int method; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_asus_softc *)oidp->oid_arg1; function = oidp->oid_arg2; method = acpi_asus_sysctls[function].method; ACPI_SERIAL_BEGIN(asus); arg = acpi_asus_sysctl_get(sc, method); error = sysctl_handle_int(oidp, &arg, 0, req); /* Sanity check */ if (error != 0 || req->newptr == NULL) goto out; /* Update */ error = acpi_asus_sysctl_set(sc, method, arg); out: ACPI_SERIAL_END(asus); return (error); } static int acpi_asus_sysctl_get(struct acpi_asus_softc *sc, int method) { int val = 0; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(asus); switch (method) { case ACPI_ASUS_METHOD_BRN: val = sc->s_brn; break; case ACPI_ASUS_METHOD_DISP: val = sc->s_disp; break; case ACPI_ASUS_METHOD_LCD: val = sc->s_lcd; break; case ACPI_ASUS_METHOD_CAMERA: val = sc->s_cam; break; case ACPI_ASUS_METHOD_CARDRD: val = sc->s_crd; break; case ACPI_ASUS_METHOD_WLAN: val = sc->s_wlan; break; } return (val); } static int acpi_asus_sysctl_set(struct acpi_asus_softc *sc, int method, int arg) { ACPI_STATUS status = AE_OK; ACPI_OBJECT_LIST acpiargs; ACPI_OBJECT acpiarg[1]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(asus); acpiargs.Count = 1; acpiargs.Pointer = acpiarg; acpiarg[0].Type = ACPI_TYPE_INTEGER; acpiarg[0].Integer.Value = arg; switch (method) { case ACPI_ASUS_METHOD_BRN: if (arg < 0 || arg > 15) return (EINVAL); if (sc->model->brn_set) status = acpi_SetInteger(sc->handle, sc->model->brn_set, arg); else { while (arg != 0) { status = AcpiEvaluateObject(sc->handle, (arg > 0) ? sc->model->brn_up : sc->model->brn_dn, NULL, NULL); (arg > 0) ? arg-- : arg++; } } if (ACPI_SUCCESS(status)) sc->s_brn = arg; break; case ACPI_ASUS_METHOD_DISP: if (arg < 0 || arg > 7) return (EINVAL); status = acpi_SetInteger(sc->handle, sc->model->disp_set, arg); if (ACPI_SUCCESS(status)) sc->s_disp = arg; break; case ACPI_ASUS_METHOD_LCD: if (arg < 0 || arg > 1) return (EINVAL); if (strncmp(sc->model->name, "L3H", 3) != 0) status = AcpiEvaluateObject(sc->handle, sc->model->lcd_set, NULL, NULL); else status = acpi_SetInteger(sc->handle, sc->model->lcd_set, 0x7); if (ACPI_SUCCESS(status)) sc->s_lcd = arg; break; case ACPI_ASUS_METHOD_CAMERA: if (arg < 0 || arg > 1) return (EINVAL); status = AcpiEvaluateObject(sc->handle, sc->model->cam_set, &acpiargs, NULL); if (ACPI_SUCCESS(status)) sc->s_cam = arg; break; case ACPI_ASUS_METHOD_CARDRD: if (arg < 0 || arg > 1) return (EINVAL); status = AcpiEvaluateObject(sc->handle, sc->model->crd_set, &acpiargs, NULL); if (ACPI_SUCCESS(status)) sc->s_crd = arg; break; case ACPI_ASUS_METHOD_WLAN: if (arg < 0 || arg > 1) return (EINVAL); status = AcpiEvaluateObject(sc->handle, sc->model->wlan_set, &acpiargs, NULL); if (ACPI_SUCCESS(status)) sc->s_wlan = arg; break; } return (0); } static int acpi_asus_sysctl_init(struct acpi_asus_softc *sc, int method) { ACPI_STATUS status; switch (method) { case ACPI_ASUS_METHOD_BRN: if (sc->model->brn_get) { /* GPLV/SPLV models */ status = acpi_GetInteger(sc->handle, sc->model->brn_get, &sc->s_brn); if (ACPI_SUCCESS(status)) return (TRUE); } else if (sc->model->brn_up) { /* Relative models */ status = AcpiEvaluateObject(sc->handle, sc->model->brn_up, NULL, NULL); if (ACPI_FAILURE(status)) return (FALSE); status = AcpiEvaluateObject(sc->handle, sc->model->brn_dn, NULL, NULL); if (ACPI_FAILURE(status)) return (FALSE); return (TRUE); } return (FALSE); case ACPI_ASUS_METHOD_DISP: if (sc->model->disp_get) { status = acpi_GetInteger(sc->handle, sc->model->disp_get, &sc->s_disp); if (ACPI_SUCCESS(status)) return (TRUE); } return (FALSE); case ACPI_ASUS_METHOD_LCD: if (sc->model->lcd_get) { if (strncmp(sc->model->name, "L3H", 3) == 0) { ACPI_BUFFER Buf; ACPI_OBJECT Arg[2], Obj; ACPI_OBJECT_LIST Args; /* L3H is a bit special */ Arg[0].Type = ACPI_TYPE_INTEGER; Arg[0].Integer.Value = 0x02; Arg[1].Type = ACPI_TYPE_INTEGER; Arg[1].Integer.Value = 0x03; Args.Count = 2; Args.Pointer = Arg; Buf.Length = sizeof(Obj); Buf.Pointer = &Obj; status = AcpiEvaluateObject(sc->handle, sc->model->lcd_get, &Args, &Buf); if (ACPI_SUCCESS(status) && Obj.Type == ACPI_TYPE_INTEGER) { sc->s_lcd = Obj.Integer.Value >> 8; return (TRUE); } } else { status = acpi_GetInteger(sc->handle, sc->model->lcd_get, &sc->s_lcd); if (ACPI_SUCCESS(status)) return (TRUE); } } return (FALSE); case ACPI_ASUS_METHOD_CAMERA: if (sc->model->cam_get) { status = acpi_GetInteger(sc->handle, sc->model->cam_get, &sc->s_cam); if (ACPI_SUCCESS(status)) return (TRUE); } return (FALSE); case ACPI_ASUS_METHOD_CARDRD: if (sc->model->crd_get) { status = acpi_GetInteger(sc->handle, sc->model->crd_get, &sc->s_crd); if (ACPI_SUCCESS(status)) return (TRUE); } return (FALSE); case ACPI_ASUS_METHOD_WLAN: if (sc->model->wlan_get) { status = acpi_GetInteger(sc->handle, sc->model->wlan_get, &sc->s_wlan); if (ACPI_SUCCESS(status)) return (TRUE); } return (FALSE); } return (FALSE); } static void acpi_asus_notify(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_asus_softc *sc; struct acpi_softc *acpi_sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc((device_t)context); acpi_sc = acpi_device_get_parent_softc(sc->dev); ACPI_SERIAL_BEGIN(asus); if ((notify & ~0x10) <= 15) { sc->s_brn = notify & ~0x10; ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); } else if ((notify & ~0x20) <= 15) { sc->s_brn = notify & ~0x20; ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); } else if (notify == 0x33) { sc->s_lcd = 1; ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned on\n"); } else if (notify == 0x34) { sc->s_lcd = 0; ACPI_VPRINT(sc->dev, acpi_sc, "LCD turned off\n"); } else if (notify == 0x86) { acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn-1); ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); } else if (notify == 0x87) { acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn+1); ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); } else { /* Notify devd(8) */ acpi_UserNotify("ASUS", h, notify); } ACPI_SERIAL_END(asus); } static void acpi_asus_lcdd_notify(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_asus_softc *sc; struct acpi_softc *acpi_sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc((device_t)context); acpi_sc = acpi_device_get_parent_softc(sc->dev); ACPI_SERIAL_BEGIN(asus); switch (notify) { case 0x87: acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn-1); ACPI_VPRINT(sc->dev, acpi_sc, "Brightness decreased\n"); break; case 0x86: acpi_asus_sysctl_set(sc, ACPI_ASUS_METHOD_BRN, sc->s_brn+1); ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased\n"); break; } ACPI_SERIAL_END(asus); } static void acpi_asus_eeepc_notify(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_asus_softc *sc; struct acpi_softc *acpi_sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc((device_t)context); acpi_sc = acpi_device_get_parent_softc(sc->dev); ACPI_SERIAL_BEGIN(asus); if ((notify & ~0x20) <= 15) { sc->s_brn = notify & ~0x20; ACPI_VPRINT(sc->dev, acpi_sc, "Brightness increased/decreased\n"); } else { /* Notify devd(8) */ acpi_UserNotify("ASUS-Eee", h, notify); } ACPI_SERIAL_END(asus); } Index: head/sys/dev/acpi_support/acpi_fujitsu.c =================================================================== --- head/sys/dev/acpi_support/acpi_fujitsu.c (revision 339753) +++ head/sys/dev/acpi_support/acpi_fujitsu.c (revision 339754) @@ -1,768 +1,767 @@ /*- * Copyright (c) 2002 Sean Bullington * 2003-2008 Anish Mistry * 2004 Mark Santcroos * 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. * */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("Fujitsu") /* Change and update bits for the hotkeys */ #define VOLUME_MUTE_BIT 0x40000000 /* Values of settings */ #define GENERAL_SETTING_BITS 0x0fffffff #define MOUSE_SETTING_BITS GENERAL_SETTING_BITS #define VOLUME_SETTING_BITS GENERAL_SETTING_BITS #define BRIGHTNESS_SETTING_BITS GENERAL_SETTING_BITS /* Possible state changes */ /* * These are NOT arbitrary values. They are the * GHKS return value from the device that says which * hotkey is active. They should match up with a bit * from the GSIF bitmask. */ #define BRIGHT_CHANGED 0x01 #define VOLUME_CHANGED 0x04 #define MOUSE_CHANGED 0x08 /* * It is unknown which hotkey this bit is supposed to indicate, but * according to values from GSIF this is a valid flag. */ #define UNKNOWN_CHANGED 0x10 /* sysctl values */ #define FN_MUTE 0 #define FN_POINTER_ENABLE 1 #define FN_LCD_BRIGHTNESS 2 #define FN_VOLUME 3 /* Methods */ #define METHOD_GBLL 1 #define METHOD_GMOU 2 #define METHOD_GVOL 3 #define METHOD_MUTE 4 #define METHOD_RBLL 5 #define METHOD_RVOL 6 #define METHOD_GSIF 7 #define METHOD_GHKS 8 #define METHOD_GBLS 9 /* Notify event */ #define ACPI_NOTIFY_STATUS_CHANGED 0x80 /* * Holds a control method name and its associated integer value. * Only used for no-argument control methods which return a value. */ struct int_nameval { char *name; int value; int exists; }; /* * Driver extension for the FUJITSU ACPI driver. */ struct acpi_fujitsu_softc { device_t dev; ACPI_HANDLE handle; /* Control methods */ struct int_nameval _sta, /* unused */ gbll, /* brightness */ gbls, /* get brightness state */ ghks, /* hotkey selector */ gbuf, /* unused (buffer?) */ gmou, /* mouse */ gsif, /* function key bitmask */ gvol, /* volume */ rbll, /* number of brightness levels (radix) */ rvol; /* number of volume levels (radix) */ /* State variables */ uint8_t bIsMuted; /* Is volume muted */ uint8_t bIntPtrEnabled; /* Is internal ptr enabled */ uint32_t lastValChanged; /* The last value updated */ /* sysctl tree */ struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; }; /* Driver entry point forward declarations. */ static int acpi_fujitsu_probe(device_t dev); static int acpi_fujitsu_attach(device_t dev); static int acpi_fujitsu_detach(device_t dev); static int acpi_fujitsu_suspend(device_t dev); static int acpi_fujitsu_resume(device_t dev); static void acpi_fujitsu_notify_status_changed(void *arg); static void acpi_fujitsu_notify_handler(ACPI_HANDLE h, uint32_t notify, void *context); static int acpi_fujitsu_sysctl(SYSCTL_HANDLER_ARGS); /* Utility function declarations */ static uint8_t acpi_fujitsu_update(struct acpi_fujitsu_softc *sc); static uint8_t acpi_fujitsu_init(struct acpi_fujitsu_softc *sc); static uint8_t acpi_fujitsu_check_hardware(struct acpi_fujitsu_softc *sc); /* Driver/Module specific structure definitions. */ static device_method_t acpi_fujitsu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_fujitsu_probe), DEVMETHOD(device_attach, acpi_fujitsu_attach), DEVMETHOD(device_detach, acpi_fujitsu_detach), DEVMETHOD(device_suspend, acpi_fujitsu_suspend), DEVMETHOD(device_resume, acpi_fujitsu_resume), DEVMETHOD_END }; static driver_t acpi_fujitsu_driver = { "acpi_fujitsu", acpi_fujitsu_methods, sizeof(struct acpi_fujitsu_softc), }; /* Prototype for function hotkeys for getting/setting a value. */ static int acpi_fujitsu_method_get(struct acpi_fujitsu_softc *sc, int method); static int acpi_fujitsu_method_set(struct acpi_fujitsu_softc *sc, int method, int value); static char *fujitsu_ids[] = { "FUJ02B1", NULL }; ACPI_SERIAL_DECL(fujitsu, "Fujitsu Function Hotkeys"); /* sysctl names and function calls */ static struct { char *name; int method; char *description; } sysctl_table[] = { { .name = "mute", .method = METHOD_MUTE, .description = "Speakers/headphones mute status" }, { .name = "pointer_enable", .method = METHOD_GMOU, .description = "Enable and disable the internal pointer" }, { .name = "lcd_brightness", .method = METHOD_GBLL, .description = "Brightness level of the LCD panel" }, { .name = "lcd_brightness", .method = METHOD_GBLS, .description = "Brightness level of the LCD panel" }, { .name = "volume", .method = METHOD_GVOL, .description = "Speakers/headphones volume level" }, { .name = "volume_radix", .method = METHOD_RVOL, .description = "Number of volume level steps" }, { .name = "lcd_brightness_radix", .method = METHOD_RBLL, .description = "Number of brightness level steps" }, { NULL, 0, NULL } }; static devclass_t acpi_fujitsu_devclass; DRIVER_MODULE(acpi_fujitsu, acpi, acpi_fujitsu_driver, acpi_fujitsu_devclass, 0, 0); MODULE_DEPEND(acpi_fujitsu, acpi, 1, 1, 1); MODULE_VERSION(acpi_fujitsu, 1); static int acpi_fujitsu_probe(device_t dev) { char *name; char buffer[64]; + int rv; - name = ACPI_ID_PROBE(device_get_parent(dev), dev, fujitsu_ids); - if (acpi_disabled("fujitsu") || name == NULL || - device_get_unit(dev) > 1) + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, fujitsu_ids, &name); + if (acpi_disabled("fujitsu") || rv > 0 || device_get_unit(dev) > 1) return (ENXIO); - sprintf(buffer, "Fujitsu Function Hotkeys %s", name); device_set_desc_copy(dev, buffer); - return (0); + return (rv); } static int acpi_fujitsu_attach(device_t dev) { struct acpi_fujitsu_softc *sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->dev = dev; sc->handle = acpi_get_handle(dev); /* Install notification handler */ AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_fujitsu_notify_handler, sc); /* Snag our default values for the hotkeys / hotkey states. */ ACPI_SERIAL_BEGIN(fujitsu); if (!acpi_fujitsu_init(sc)) device_printf(dev, "Couldn't initialize hotkey states!\n"); ACPI_SERIAL_END(fujitsu); return (0); } /* * Called when the system is being suspended, simply * set an event to be signalled when we wake up. */ static int acpi_fujitsu_suspend(device_t dev) { return (0); } static int acpi_fujitsu_resume(device_t dev) { struct acpi_fujitsu_softc *sc; ACPI_STATUS status; sc = device_get_softc(dev); /* * The pointer needs to be re-enabled for * some revisions of the P series (2120). */ ACPI_SERIAL_BEGIN(fujitsu); if(sc->gmou.exists) { status = acpi_SetInteger(sc->handle, "SMOU", 1); if (ACPI_FAILURE(status)) device_printf(sc->dev, "Couldn't enable pointer\n"); } ACPI_SERIAL_END(fujitsu); return (0); } static void acpi_fujitsu_notify_status_changed(void *arg) { struct acpi_fujitsu_softc *sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_fujitsu_softc *)arg; /* * Since our notify function is called, we know something has * happened. So the only reason for acpi_fujitsu_update to fail * is if we can't find what has changed or an error occurs. */ ACPI_SERIAL_BEGIN(fujitsu); acpi_fujitsu_update(sc); ACPI_SERIAL_END(fujitsu); } static void acpi_fujitsu_notify_handler(ACPI_HANDLE h, uint32_t notify, void *context) { struct acpi_fujitsu_softc *sc; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); sc = (struct acpi_fujitsu_softc *)context; switch (notify) { case ACPI_NOTIFY_STATUS_CHANGED: AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_fujitsu_notify_status_changed, sc); break; default: /* unknown notification value */ break; } } static int acpi_fujitsu_detach(device_t dev) { struct acpi_fujitsu_softc *sc; sc = device_get_softc(dev); AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_fujitsu_notify_handler); sysctl_ctx_free(&sc->sysctl_ctx); return (0); } /* * Initializes the names of the ACPI control methods and grabs * the current state of all of the ACPI hotkeys into the softc. */ static uint8_t acpi_fujitsu_init(struct acpi_fujitsu_softc *sc) { struct acpi_softc *acpi_sc; int i, exists; ACPI_SERIAL_ASSERT(fujitsu); /* Setup all of the names for each control method */ sc->_sta.name = "_STA"; sc->gbll.name = "GBLL"; sc->gbls.name = "GBLS"; sc->ghks.name = "GHKS"; sc->gmou.name = "GMOU"; sc->gsif.name = "GSIF"; sc->gvol.name = "GVOL"; sc->ghks.name = "GHKS"; sc->gsif.name = "GSIF"; sc->rbll.name = "RBLL"; sc->rvol.name = "RVOL"; /* Determine what hardware functionality is available */ acpi_fujitsu_check_hardware(sc); /* Build the sysctl tree */ acpi_sc = acpi_device_get_parent_softc(sc->dev); sysctl_ctx_init(&sc->sysctl_ctx); sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "fujitsu", CTLFLAG_RD, 0, ""); for (i = 0; sysctl_table[i].name != NULL; i++) { switch(sysctl_table[i].method) { case METHOD_GMOU: exists = sc->gmou.exists; break; case METHOD_GBLL: exists = sc->gbll.exists; break; case METHOD_GBLS: exists = sc->gbls.exists; break; case METHOD_GVOL: case METHOD_MUTE: exists = sc->gvol.exists; break; case METHOD_RVOL: exists = sc->rvol.exists; break; case METHOD_RBLL: exists = sc->rbll.exists; break; default: /* Allow by default */ exists = 1; break; } if(!exists) continue; SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, sysctl_table[i].name, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, i, acpi_fujitsu_sysctl, "I", sysctl_table[i].description); } /* Set the hotkeys to their initial states */ if (!acpi_fujitsu_update(sc)) { device_printf(sc->dev, "Couldn't init hotkey states\n"); return (FALSE); } return (TRUE); } static int acpi_fujitsu_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_fujitsu_softc *sc; int method; int arg; int function_num, error = 0; sc = (struct acpi_fujitsu_softc *)oidp->oid_arg1; function_num = oidp->oid_arg2; method = sysctl_table[function_num].method; ACPI_SERIAL_BEGIN(fujitsu); /* Get the current value */ arg = acpi_fujitsu_method_get(sc, method); error = sysctl_handle_int(oidp, &arg, 0, req); if (error != 0 || req->newptr == NULL) goto out; /* Update the value */ error = acpi_fujitsu_method_set(sc, method, arg); out: ACPI_SERIAL_END(fujitsu); return (error); } static int acpi_fujitsu_method_get(struct acpi_fujitsu_softc *sc, int method) { struct int_nameval nv; ACPI_STATUS status; ACPI_SERIAL_ASSERT(fujitsu); switch (method) { case METHOD_GBLL: nv = sc->gbll; break; case METHOD_GBLS: nv = sc->gbls; break; case METHOD_GMOU: nv = sc->gmou; break; case METHOD_GVOL: case METHOD_MUTE: nv = sc->gvol; break; case METHOD_GHKS: nv = sc->ghks; break; case METHOD_GSIF: nv = sc->gsif; break; case METHOD_RBLL: nv = sc->rbll; break; case METHOD_RVOL: nv = sc->rvol; break; default: return (FALSE); } if(!nv.exists) return (EINVAL); status = acpi_GetInteger(sc->handle, nv.name, &nv.value); if (ACPI_FAILURE(status)) { device_printf(sc->dev, "Couldn't query method (%s)\n", nv.name); return (FALSE); } if (method == METHOD_MUTE) { sc->bIsMuted = (uint8_t)((nv.value & VOLUME_MUTE_BIT) != 0); return (sc->bIsMuted); } nv.value &= GENERAL_SETTING_BITS; return (nv.value); } static int acpi_fujitsu_method_set(struct acpi_fujitsu_softc *sc, int method, int value) { struct int_nameval nv; ACPI_STATUS status; char *control; int changed; ACPI_SERIAL_ASSERT(fujitsu); switch (method) { case METHOD_GBLL: changed = BRIGHT_CHANGED; control = "SBLL"; nv = sc->gbll; break; case METHOD_GBLS: changed = BRIGHT_CHANGED; control = "SBL2"; nv = sc->gbls; break; case METHOD_GMOU: changed = MOUSE_CHANGED; control = "SMOU"; nv = sc->gmou; break; case METHOD_GVOL: case METHOD_MUTE: changed = VOLUME_CHANGED; control = "SVOL"; nv = sc->gvol; break; default: return (EINVAL); } if(!nv.exists) return (EINVAL); if (method == METHOD_MUTE) { if (value == 1) value = nv.value | VOLUME_MUTE_BIT; else if (value == 0) value = nv.value & ~VOLUME_MUTE_BIT; else return (EINVAL); } status = acpi_SetInteger(sc->handle, control, value); if (ACPI_FAILURE(status)) { device_printf(sc->dev, "Couldn't update %s\n", control); return (FALSE); } sc->lastValChanged = changed; return (0); } /* * Query the get methods to determine what functionality is available * from the hardware function hotkeys. */ static uint8_t acpi_fujitsu_check_hardware(struct acpi_fujitsu_softc *sc) { int val; ACPI_SERIAL_ASSERT(fujitsu); /* save the hotkey bitmask */ if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->gsif.name, &(sc->gsif.value)))) { sc->gsif.exists = 0; device_printf(sc->dev, "Couldn't query bitmask value\n"); } else { sc->gsif.exists = 1; } /* System Volume Level */ if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->gvol.name, &val))) { sc->gvol.exists = 0; } else { sc->gvol.exists = 1; } if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->gbls.name, &val))) { sc->gbls.exists = 0; } else { sc->gbls.exists = 1; } // don't add if we can use the new method if (sc->gbls.exists || ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->gbll.name, &val))) { sc->gbll.exists = 0; } else { sc->gbll.exists = 1; } if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->ghks.name, &val))) { sc->ghks.exists = 0; } else { sc->ghks.exists = 1; } if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->gmou.name, &val))) { sc->gmou.exists = 0; } else { sc->gmou.exists = 1; } if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->rbll.name, &val))) { sc->rbll.exists = 0; } else { sc->rbll.exists = 1; } if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->rvol.name, &val))) { sc->rvol.exists = 0; } else { sc->rvol.exists = 1; } return (TRUE); } /* * Query each of the ACPI control methods that contain information we're * interested in. We check the return values from the control methods and * adjust any state variables if they should be adjusted. */ static uint8_t acpi_fujitsu_update(struct acpi_fujitsu_softc *sc) { int changed; struct acpi_softc *acpi_sc; acpi_sc = acpi_device_get_parent_softc(sc->dev); ACPI_SERIAL_ASSERT(fujitsu); if(sc->gsif.exists) changed = sc->gsif.value & acpi_fujitsu_method_get(sc,METHOD_GHKS); else changed = 0; /* System Volume Level */ if(sc->gvol.exists) { if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->gvol.name, &(sc->gvol.value)))) { device_printf(sc->dev, "Couldn't query volume level\n"); return (FALSE); } if (changed & VOLUME_CHANGED) { sc->bIsMuted = (uint8_t)((sc->gvol.value & VOLUME_MUTE_BIT) != 0); /* Clear the modification bit */ sc->gvol.value &= VOLUME_SETTING_BITS; if (sc->bIsMuted) { acpi_UserNotify("FUJITSU", sc->handle, FN_MUTE); ACPI_VPRINT(sc->dev, acpi_sc, "Volume is now mute\n"); } else ACPI_VPRINT(sc->dev, acpi_sc, "Volume is now %d\n", sc->gvol.value); acpi_UserNotify("FUJITSU", sc->handle, FN_VOLUME); } } /* Internal mouse pointer (eraserhead) */ if(sc->gmou.exists) { if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->gmou.name, &(sc->gmou.value)))) { device_printf(sc->dev, "Couldn't query pointer state\n"); return (FALSE); } if (changed & MOUSE_CHANGED) { sc->bIntPtrEnabled = (uint8_t)(sc->gmou.value & 0x1); /* Clear the modification bit */ sc->gmou.value &= MOUSE_SETTING_BITS; /* Set the value in case it is not hardware controlled */ acpi_fujitsu_method_set(sc, METHOD_GMOU, sc->gmou.value); acpi_UserNotify("FUJITSU", sc->handle, FN_POINTER_ENABLE); ACPI_VPRINT(sc->dev, acpi_sc, "Internal pointer is now %s\n", (sc->bIntPtrEnabled) ? "enabled" : "disabled"); } } /* Screen Brightness Level P8XXX */ if(sc->gbls.exists) { if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->gbls.name, &(sc->gbls.value)))) { device_printf(sc->dev, "Couldn't query P8XXX brightness level\n"); return (FALSE); } if (changed & BRIGHT_CHANGED) { /* No state to record here. */ /* Clear the modification bit */ sc->gbls.value &= BRIGHTNESS_SETTING_BITS; /* Set the value in case it is not hardware controlled */ acpi_fujitsu_method_set(sc, METHOD_GBLS, sc->gbls.value); acpi_UserNotify("FUJITSU", sc->handle, FN_LCD_BRIGHTNESS); ACPI_VPRINT(sc->dev, acpi_sc, "P8XXX Brightness level is now %d\n", sc->gbls.value); } } /* Screen Brightness Level */ if(sc->gbll.exists) { if (ACPI_FAILURE(acpi_GetInteger(sc->handle, sc->gbll.name, &(sc->gbll.value)))) { device_printf(sc->dev, "Couldn't query brightness level\n"); return (FALSE); } if (changed & BRIGHT_CHANGED) { /* No state to record here. */ /* Clear the modification bit */ sc->gbll.value &= BRIGHTNESS_SETTING_BITS; acpi_UserNotify("FUJITSU", sc->handle, FN_LCD_BRIGHTNESS); ACPI_VPRINT(sc->dev, acpi_sc, "Brightness level is now %d\n", sc->gbll.value); } } sc->lastValChanged = changed; return (TRUE); } Index: head/sys/dev/acpi_support/acpi_ibm.c =================================================================== --- head/sys/dev/acpi_support/acpi_ibm.c (revision 339753) +++ head/sys/dev/acpi_support/acpi_ibm.c (revision 339754) @@ -1,1377 +1,1380 @@ /*- * Copyright (c) 2004 Takanori Watanabe * Copyright (c) 2005 Markus Brueffer * 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. */ #include __FBSDID("$FreeBSD$"); /* * Driver for extra ACPI-controlled gadgets found on IBM ThinkPad laptops. * Inspired by the ibm-acpi and tpb projects which implement these features * on Linux. * * acpi-ibm: * tpb: */ #include "opt_acpi.h" #include #include #include #include #include #include #include #include "acpi_if.h" #include #include #include #include #include #include #include #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("IBM") /* Internal methods */ #define ACPI_IBM_METHOD_EVENTS 1 #define ACPI_IBM_METHOD_EVENTMASK 2 #define ACPI_IBM_METHOD_HOTKEY 3 #define ACPI_IBM_METHOD_BRIGHTNESS 4 #define ACPI_IBM_METHOD_VOLUME 5 #define ACPI_IBM_METHOD_MUTE 6 #define ACPI_IBM_METHOD_THINKLIGHT 7 #define ACPI_IBM_METHOD_BLUETOOTH 8 #define ACPI_IBM_METHOD_WLAN 9 #define ACPI_IBM_METHOD_FANSPEED 10 #define ACPI_IBM_METHOD_FANLEVEL 11 #define ACPI_IBM_METHOD_FANSTATUS 12 #define ACPI_IBM_METHOD_THERMAL 13 #define ACPI_IBM_METHOD_HANDLEREVENTS 14 #define ACPI_IBM_METHOD_MIC_LED 15 /* Hotkeys/Buttons */ #define IBM_RTC_HOTKEY1 0x64 #define IBM_RTC_MASK_HOME (1 << 0) #define IBM_RTC_MASK_SEARCH (1 << 1) #define IBM_RTC_MASK_MAIL (1 << 2) #define IBM_RTC_MASK_WLAN (1 << 5) #define IBM_RTC_HOTKEY2 0x65 #define IBM_RTC_MASK_THINKPAD (1 << 3) #define IBM_RTC_MASK_ZOOM (1 << 5) #define IBM_RTC_MASK_VIDEO (1 << 6) #define IBM_RTC_MASK_HIBERNATE (1 << 7) #define IBM_RTC_THINKLIGHT 0x66 #define IBM_RTC_MASK_THINKLIGHT (1 << 4) #define IBM_RTC_SCREENEXPAND 0x67 #define IBM_RTC_MASK_SCREENEXPAND (1 << 5) #define IBM_RTC_BRIGHTNESS 0x6c #define IBM_RTC_MASK_BRIGHTNESS (1 << 5) #define IBM_RTC_VOLUME 0x6e #define IBM_RTC_MASK_VOLUME (1 << 7) /* Embedded Controller registers */ #define IBM_EC_BRIGHTNESS 0x31 #define IBM_EC_MASK_BRI 0x7 #define IBM_EC_VOLUME 0x30 #define IBM_EC_MASK_VOL 0xf #define IBM_EC_MASK_MUTE (1 << 6) #define IBM_EC_FANSTATUS 0x2F #define IBM_EC_MASK_FANLEVEL 0x3f #define IBM_EC_MASK_FANDISENGAGED (1 << 6) #define IBM_EC_MASK_FANSTATUS (1 << 7) #define IBM_EC_FANSPEED 0x84 /* CMOS Commands */ #define IBM_CMOS_VOLUME_DOWN 0 #define IBM_CMOS_VOLUME_UP 1 #define IBM_CMOS_VOLUME_MUTE 2 #define IBM_CMOS_BRIGHTNESS_UP 4 #define IBM_CMOS_BRIGHTNESS_DOWN 5 /* ACPI methods */ #define IBM_NAME_KEYLIGHT "KBLT" #define IBM_NAME_WLAN_BT_GET "GBDC" #define IBM_NAME_WLAN_BT_SET "SBDC" #define IBM_NAME_MASK_BT (1 << 1) #define IBM_NAME_MASK_WLAN (1 << 2) #define IBM_NAME_THERMAL_GET "TMP7" #define IBM_NAME_THERMAL_UPDT "UPDT" #define IBM_NAME_EVENTS_STATUS_GET "DHKC" #define IBM_NAME_EVENTS_MASK_GET "DHKN" #define IBM_NAME_EVENTS_STATUS_SET "MHKC" #define IBM_NAME_EVENTS_MASK_SET "MHKM" #define IBM_NAME_EVENTS_GET "MHKP" #define IBM_NAME_EVENTS_AVAILMASK "MHKA" /* Event Code */ #define IBM_EVENT_LCD_BACKLIGHT 0x03 #define IBM_EVENT_SUSPEND_TO_RAM 0x04 #define IBM_EVENT_BLUETOOTH 0x05 #define IBM_EVENT_SCREEN_EXPAND 0x07 #define IBM_EVENT_SUSPEND_TO_DISK 0x0c #define IBM_EVENT_BRIGHTNESS_UP 0x10 #define IBM_EVENT_BRIGHTNESS_DOWN 0x11 #define IBM_EVENT_THINKLIGHT 0x12 #define IBM_EVENT_ZOOM 0x14 #define IBM_EVENT_VOLUME_UP 0x15 #define IBM_EVENT_VOLUME_DOWN 0x16 #define IBM_EVENT_MUTE 0x17 #define IBM_EVENT_ACCESS_IBM_BUTTON 0x18 #define ABS(x) (((x) < 0)? -(x) : (x)) struct acpi_ibm_softc { device_t dev; ACPI_HANDLE handle; /* Embedded controller */ device_t ec_dev; ACPI_HANDLE ec_handle; /* CMOS */ ACPI_HANDLE cmos_handle; /* Fan status */ ACPI_HANDLE fan_handle; int fan_levels; /* Keylight commands and states */ ACPI_HANDLE light_handle; int light_cmd_on; int light_cmd_off; int light_val; int light_get_supported; int light_set_supported; /* led(4) interface */ struct cdev *led_dev; int led_busy; int led_state; /* Mic led handle */ ACPI_HANDLE mic_led_handle; int mic_led_state; int wlan_bt_flags; int thermal_updt_supported; unsigned int events_availmask; unsigned int events_initialmask; int events_mask_supported; int events_enable; unsigned int handler_events; struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; }; static struct { char *name; int method; char *description; int flag_rdonly; } acpi_ibm_sysctls[] = { { .name = "events", .method = ACPI_IBM_METHOD_EVENTS, .description = "ACPI events enable", }, { .name = "eventmask", .method = ACPI_IBM_METHOD_EVENTMASK, .description = "ACPI eventmask", }, { .name = "hotkey", .method = ACPI_IBM_METHOD_HOTKEY, .description = "Key Status", .flag_rdonly = 1 }, { .name = "lcd_brightness", .method = ACPI_IBM_METHOD_BRIGHTNESS, .description = "LCD Brightness", }, { .name = "volume", .method = ACPI_IBM_METHOD_VOLUME, .description = "Volume", }, { .name = "mute", .method = ACPI_IBM_METHOD_MUTE, .description = "Mute", }, { .name = "thinklight", .method = ACPI_IBM_METHOD_THINKLIGHT, .description = "Thinklight enable", }, { .name = "bluetooth", .method = ACPI_IBM_METHOD_BLUETOOTH, .description = "Bluetooth enable", }, { .name = "wlan", .method = ACPI_IBM_METHOD_WLAN, .description = "WLAN enable", .flag_rdonly = 1 }, { .name = "fan_speed", .method = ACPI_IBM_METHOD_FANSPEED, .description = "Fan speed", .flag_rdonly = 1 }, { .name = "fan_level", .method = ACPI_IBM_METHOD_FANLEVEL, .description = "Fan level", }, { .name = "fan", .method = ACPI_IBM_METHOD_FANSTATUS, .description = "Fan enable", }, { .name = "mic_led", .method = ACPI_IBM_METHOD_MIC_LED, .description = "Mic led", }, { NULL, 0, NULL, 0 } }; /* * Per-model default list of event mask. */ #define ACPI_IBM_HKEY_RFKILL_MASK (1 << 4) #define ACPI_IBM_HKEY_DSWITCH_MASK (1 << 6) #define ACPI_IBM_HKEY_BRIGHTNESS_UP_MASK (1 << 15) #define ACPI_IBM_HKEY_BRIGHTNESS_DOWN_MASK (1 << 16) #define ACPI_IBM_HKEY_SEARCH_MASK (1 << 18) #define ACPI_IBM_HKEY_MICMUTE_MASK (1 << 26) #define ACPI_IBM_HKEY_SETTINGS_MASK (1 << 28) #define ACPI_IBM_HKEY_VIEWOPEN_MASK (1 << 30) #define ACPI_IBM_HKEY_VIEWALL_MASK (1 << 31) struct acpi_ibm_models { const char *maker; const char *product; uint32_t eventmask; } acpi_ibm_models[] = { { "LENOVO", "20BSCTO1WW", ACPI_IBM_HKEY_RFKILL_MASK | ACPI_IBM_HKEY_DSWITCH_MASK | ACPI_IBM_HKEY_BRIGHTNESS_UP_MASK | ACPI_IBM_HKEY_BRIGHTNESS_DOWN_MASK | ACPI_IBM_HKEY_SEARCH_MASK | ACPI_IBM_HKEY_MICMUTE_MASK | ACPI_IBM_HKEY_SETTINGS_MASK | ACPI_IBM_HKEY_VIEWOPEN_MASK | ACPI_IBM_HKEY_VIEWALL_MASK } }; ACPI_SERIAL_DECL(ibm, "ACPI IBM extras"); static int acpi_ibm_probe(device_t dev); static int acpi_ibm_attach(device_t dev); static int acpi_ibm_detach(device_t dev); static int acpi_ibm_resume(device_t dev); static void ibm_led(void *softc, int onoff); static void ibm_led_task(struct acpi_ibm_softc *sc, int pending __unused); static int acpi_ibm_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_ibm_sysctl_init(struct acpi_ibm_softc *sc, int method); static int acpi_ibm_sysctl_get(struct acpi_ibm_softc *sc, int method); static int acpi_ibm_sysctl_set(struct acpi_ibm_softc *sc, int method, int val); static int acpi_ibm_eventmask_set(struct acpi_ibm_softc *sc, int val); static int acpi_ibm_thermal_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_ibm_handlerevents_sysctl(SYSCTL_HANDLER_ARGS); static void acpi_ibm_notify(ACPI_HANDLE h, UINT32 notify, void *context); static int acpi_ibm_brightness_set(struct acpi_ibm_softc *sc, int arg); static int acpi_ibm_bluetooth_set(struct acpi_ibm_softc *sc, int arg); static int acpi_ibm_thinklight_set(struct acpi_ibm_softc *sc, int arg); static int acpi_ibm_volume_set(struct acpi_ibm_softc *sc, int arg); static int acpi_ibm_mute_set(struct acpi_ibm_softc *sc, int arg); static device_method_t acpi_ibm_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_ibm_probe), DEVMETHOD(device_attach, acpi_ibm_attach), DEVMETHOD(device_detach, acpi_ibm_detach), DEVMETHOD(device_resume, acpi_ibm_resume), DEVMETHOD_END }; static driver_t acpi_ibm_driver = { "acpi_ibm", acpi_ibm_methods, sizeof(struct acpi_ibm_softc), }; static devclass_t acpi_ibm_devclass; DRIVER_MODULE(acpi_ibm, acpi, acpi_ibm_driver, acpi_ibm_devclass, 0, 0); MODULE_DEPEND(acpi_ibm, acpi, 1, 1, 1); static char *ibm_ids[] = {"IBM0068", "LEN0068", NULL}; static void ibm_led(void *softc, int onoff) { struct acpi_ibm_softc* sc = (struct acpi_ibm_softc*) softc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (sc->led_busy) return; sc->led_busy = 1; sc->led_state = onoff; AcpiOsExecute(OSL_NOTIFY_HANDLER, (void *)ibm_led_task, sc); } static void ibm_led_task(struct acpi_ibm_softc *sc, int pending __unused) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_BEGIN(ibm); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_THINKLIGHT, sc->led_state); ACPI_SERIAL_END(ibm); sc->led_busy = 0; } static int acpi_ibm_mic_led_set (struct acpi_ibm_softc *sc, int arg) { ACPI_OBJECT_LIST input; ACPI_OBJECT params[1]; ACPI_STATUS status; if (arg < 0 || arg > 1) return (EINVAL); if (sc->mic_led_handle) { params[0].Type = ACPI_TYPE_INTEGER; params[0].Integer.Value = 0; /* mic led: 0 off, 2 on */ if (arg == 1) params[0].Integer.Value = 2; input.Pointer = params; input.Count = 1; status = AcpiEvaluateObject (sc->handle, "MMTS", &input, NULL); if (ACPI_SUCCESS(status)) sc->mic_led_state = arg; return(status); } return (0); } static int acpi_ibm_probe(device_t dev) { + int rv; + if (acpi_disabled("ibm") || - ACPI_ID_PROBE(device_get_parent(dev), dev, ibm_ids) == NULL || device_get_unit(dev) != 0) return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, ibm_ids, NULL); - device_set_desc(dev, "IBM ThinkPad ACPI Extras"); - - return (0); + if (rv <= 0) + device_set_desc(dev, "IBM ThinkPad ACPI Extras"); + + return (rv); } static int acpi_ibm_attach(device_t dev) { int i; struct acpi_ibm_softc *sc; char *maker, *product; devclass_t ec_devclass; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); sc = device_get_softc(dev); sc->dev = dev; sc->handle = acpi_get_handle(dev); /* Look for the first embedded controller */ if (!(ec_devclass = devclass_find ("acpi_ec"))) { if (bootverbose) device_printf(dev, "Couldn't find acpi_ec devclass\n"); return (EINVAL); } if (!(sc->ec_dev = devclass_get_device(ec_devclass, 0))) { if (bootverbose) device_printf(dev, "Couldn't find acpi_ec device\n"); return (EINVAL); } sc->ec_handle = acpi_get_handle(sc->ec_dev); /* Get the sysctl tree */ sc->sysctl_ctx = device_get_sysctl_ctx(dev); sc->sysctl_tree = device_get_sysctl_tree(dev); /* Look for event mask and hook up the nodes */ sc->events_mask_supported = ACPI_SUCCESS(acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_MASK_GET, &sc->events_initialmask)); if (sc->events_mask_supported) { SYSCTL_ADD_UINT(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "initialmask", CTLFLAG_RD, &sc->events_initialmask, 0, "Initial eventmask"); /* The availmask is the bitmask of supported events */ if (ACPI_FAILURE(acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_AVAILMASK, &sc->events_availmask))) sc->events_availmask = 0xffffffff; SYSCTL_ADD_UINT(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "availmask", CTLFLAG_RD, &sc->events_availmask, 0, "Mask of supported events"); } /* Hook up proc nodes */ for (int i = 0; acpi_ibm_sysctls[i].name != NULL; i++) { if (!acpi_ibm_sysctl_init(sc, acpi_ibm_sysctls[i].method)) continue; if (acpi_ibm_sysctls[i].flag_rdonly != 0) { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_ibm_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RD, sc, i, acpi_ibm_sysctl, "I", acpi_ibm_sysctls[i].description); } else { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, acpi_ibm_sysctls[i].name, CTLTYPE_INT | CTLFLAG_RW, sc, i, acpi_ibm_sysctl, "I", acpi_ibm_sysctls[i].description); } } /* Hook up thermal node */ if (acpi_ibm_sysctl_init(sc, ACPI_IBM_METHOD_THERMAL)) { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "thermal", CTLTYPE_INT | CTLFLAG_RD, sc, 0, acpi_ibm_thermal_sysctl, "I", "Thermal zones"); } /* Hook up handlerevents node */ if (acpi_ibm_sysctl_init(sc, ACPI_IBM_METHOD_HANDLEREVENTS)) { SYSCTL_ADD_PROC(sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, "handlerevents", CTLTYPE_STRING | CTLFLAG_RW, sc, 0, acpi_ibm_handlerevents_sysctl, "I", "devd(8) events handled by acpi_ibm"); } /* Handle notifies */ AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_ibm_notify, dev); /* Hook up light to led(4) */ if (sc->light_set_supported) sc->led_dev = led_create_state(ibm_led, sc, "thinklight", (sc->light_val ? 1 : 0)); /* Enable per-model events. */ maker = kern_getenv("smbios.system.maker"); product = kern_getenv("smbios.system.product"); if (maker == NULL || product == NULL) goto nosmbios; for (i = 0; i < nitems(acpi_ibm_models); i++) { if (strcmp(maker, acpi_ibm_models[i].maker) == 0 && strcmp(product, acpi_ibm_models[i].product) == 0) { ACPI_SERIAL_BEGIN(ibm); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTMASK, acpi_ibm_models[i].eventmask); ACPI_SERIAL_END(ibm); } } nosmbios: freeenv(maker); freeenv(product); /* Enable events by default. */ ACPI_SERIAL_BEGIN(ibm); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTS, 1); ACPI_SERIAL_END(ibm); return (0); } static int acpi_ibm_detach(device_t dev) { ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); struct acpi_ibm_softc *sc = device_get_softc(dev); /* Disable events and restore eventmask */ ACPI_SERIAL_BEGIN(ibm); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTS, 0); acpi_ibm_sysctl_set(sc, ACPI_IBM_METHOD_EVENTMASK, sc->events_initialmask); ACPI_SERIAL_END(ibm); AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_ibm_notify); if (sc->led_dev != NULL) led_destroy(sc->led_dev); return (0); } static int acpi_ibm_resume(device_t dev) { struct acpi_ibm_softc *sc = device_get_softc(dev); ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); ACPI_SERIAL_BEGIN(ibm); for (int i = 0; acpi_ibm_sysctls[i].name != NULL; i++) { int val; val = acpi_ibm_sysctl_get(sc, i); if (acpi_ibm_sysctls[i].flag_rdonly != 0) continue; acpi_ibm_sysctl_set(sc, i, val); } ACPI_SERIAL_END(ibm); /* The mic led does not turn back on when sysctl_set is called in the above loop */ acpi_ibm_mic_led_set(sc, sc->mic_led_state); return (0); } static int acpi_ibm_eventmask_set(struct acpi_ibm_softc *sc, int val) { ACPI_OBJECT arg[2]; ACPI_OBJECT_LIST args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); args.Count = 2; args.Pointer = arg; arg[0].Type = ACPI_TYPE_INTEGER; arg[1].Type = ACPI_TYPE_INTEGER; for (int i = 0; i < 32; ++i) { arg[0].Integer.Value = i+1; arg[1].Integer.Value = (((1 << i) & val) != 0); status = AcpiEvaluateObject(sc->handle, IBM_NAME_EVENTS_MASK_SET, &args, NULL); if (ACPI_FAILURE(status)) return (status); } return (0); } static int acpi_ibm_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_ibm_softc *sc; int arg; int error = 0; int function; int method; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_ibm_softc *)oidp->oid_arg1; function = oidp->oid_arg2; method = acpi_ibm_sysctls[function].method; ACPI_SERIAL_BEGIN(ibm); arg = acpi_ibm_sysctl_get(sc, method); error = sysctl_handle_int(oidp, &arg, 0, req); /* Sanity check */ if (error != 0 || req->newptr == NULL) goto out; /* Update */ error = acpi_ibm_sysctl_set(sc, method, arg); out: ACPI_SERIAL_END(ibm); return (error); } static int acpi_ibm_sysctl_get(struct acpi_ibm_softc *sc, int method) { UINT64 val_ec; int val = 0, key; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); switch (method) { case ACPI_IBM_METHOD_EVENTS: acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_STATUS_GET, &val); break; case ACPI_IBM_METHOD_EVENTMASK: if (sc->events_mask_supported) acpi_GetInteger(sc->handle, IBM_NAME_EVENTS_MASK_GET, &val); break; case ACPI_IBM_METHOD_HOTKEY: /* * Construct the hotkey as a bitmask as illustrated below. * Note that whenever a key was pressed, the respecting bit * toggles and nothing else changes. * +--+--+-+-+-+-+-+-+-+-+-+-+ * |11|10|9|8|7|6|5|4|3|2|1|0| * +--+--+-+-+-+-+-+-+-+-+-+-+ * | | | | | | | | | | | | * | | | | | | | | | | | +- Home Button * | | | | | | | | | | +--- Search Button * | | | | | | | | | +----- Mail Button * | | | | | | | | +------- Thinkpad Button * | | | | | | | +--------- Zoom (Fn + Space) * | | | | | | +----------- WLAN Button * | | | | | +------------- Video Button * | | | | +--------------- Hibernate Button * | | | +----------------- Thinklight Button * | | +------------------- Screen expand (Fn + F8) * | +--------------------- Brightness * +------------------------ Volume/Mute */ key = rtcin(IBM_RTC_HOTKEY1); val = (IBM_RTC_MASK_HOME | IBM_RTC_MASK_SEARCH | IBM_RTC_MASK_MAIL | IBM_RTC_MASK_WLAN) & key; key = rtcin(IBM_RTC_HOTKEY2); val |= (IBM_RTC_MASK_THINKPAD | IBM_RTC_MASK_VIDEO | IBM_RTC_MASK_HIBERNATE) & key; val |= (IBM_RTC_MASK_ZOOM & key) >> 1; key = rtcin(IBM_RTC_THINKLIGHT); val |= (IBM_RTC_MASK_THINKLIGHT & key) << 4; key = rtcin(IBM_RTC_SCREENEXPAND); val |= (IBM_RTC_MASK_THINKLIGHT & key) << 4; key = rtcin(IBM_RTC_BRIGHTNESS); val |= (IBM_RTC_MASK_BRIGHTNESS & key) << 5; key = rtcin(IBM_RTC_VOLUME); val |= (IBM_RTC_MASK_VOLUME & key) << 4; break; case ACPI_IBM_METHOD_BRIGHTNESS: ACPI_EC_READ(sc->ec_dev, IBM_EC_BRIGHTNESS, &val_ec, 1); val = val_ec & IBM_EC_MASK_BRI; break; case ACPI_IBM_METHOD_VOLUME: ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); val = val_ec & IBM_EC_MASK_VOL; break; case ACPI_IBM_METHOD_MUTE: ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); val = ((val_ec & IBM_EC_MASK_MUTE) == IBM_EC_MASK_MUTE); break; case ACPI_IBM_METHOD_THINKLIGHT: if (sc->light_get_supported) acpi_GetInteger(sc->ec_handle, IBM_NAME_KEYLIGHT, &val); else val = sc->light_val; break; case ACPI_IBM_METHOD_BLUETOOTH: acpi_GetInteger(sc->handle, IBM_NAME_WLAN_BT_GET, &val); sc->wlan_bt_flags = val; val = ((val & IBM_NAME_MASK_BT) != 0); break; case ACPI_IBM_METHOD_WLAN: acpi_GetInteger(sc->handle, IBM_NAME_WLAN_BT_GET, &val); sc->wlan_bt_flags = val; val = ((val & IBM_NAME_MASK_WLAN) != 0); break; case ACPI_IBM_METHOD_FANSPEED: if (sc->fan_handle) { if(ACPI_FAILURE(acpi_GetInteger(sc->fan_handle, NULL, &val))) val = -1; } else { ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSPEED, &val_ec, 2); val = val_ec; } break; case ACPI_IBM_METHOD_FANLEVEL: /* * The IBM_EC_FANSTATUS register works as follows: * Bit 0-5 indicate the level at which the fan operates. Only * values between 0 and 7 have an effect. Everything * above 7 is treated the same as level 7 * Bit 6 overrides the fan speed limit if set to 1 * Bit 7 indicates at which mode the fan operates: * manual (0) or automatic (1) */ if (!sc->fan_handle) { ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); val = val_ec & IBM_EC_MASK_FANLEVEL; } break; case ACPI_IBM_METHOD_FANSTATUS: if (!sc->fan_handle) { ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); val = (val_ec & IBM_EC_MASK_FANSTATUS) == IBM_EC_MASK_FANSTATUS; } else val = -1; break; case ACPI_IBM_METHOD_MIC_LED: if (sc->mic_led_handle) return sc->mic_led_state; else val = -1; break; } return (val); } static int acpi_ibm_sysctl_set(struct acpi_ibm_softc *sc, int method, int arg) { int val; UINT64 val_ec; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); switch (method) { case ACPI_IBM_METHOD_EVENTS: if (arg < 0 || arg > 1) return (EINVAL); status = acpi_SetInteger(sc->handle, IBM_NAME_EVENTS_STATUS_SET, arg); if (ACPI_FAILURE(status)) return (status); if (sc->events_mask_supported) return acpi_ibm_eventmask_set(sc, sc->events_availmask); break; case ACPI_IBM_METHOD_EVENTMASK: if (sc->events_mask_supported) return acpi_ibm_eventmask_set(sc, arg); break; case ACPI_IBM_METHOD_BRIGHTNESS: return acpi_ibm_brightness_set(sc, arg); break; case ACPI_IBM_METHOD_VOLUME: return acpi_ibm_volume_set(sc, arg); break; case ACPI_IBM_METHOD_MUTE: return acpi_ibm_mute_set(sc, arg); break; case ACPI_IBM_METHOD_MIC_LED: return acpi_ibm_mic_led_set (sc, arg); break; case ACPI_IBM_METHOD_THINKLIGHT: return acpi_ibm_thinklight_set(sc, arg); break; case ACPI_IBM_METHOD_BLUETOOTH: return acpi_ibm_bluetooth_set(sc, arg); break; case ACPI_IBM_METHOD_FANLEVEL: if (arg < 0 || arg > 7) return (EINVAL); if (!sc->fan_handle) { /* Read the current fanstatus */ ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); val = val_ec & (~IBM_EC_MASK_FANLEVEL); return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_FANSTATUS, val | arg, 1); } break; case ACPI_IBM_METHOD_FANSTATUS: if (arg < 0 || arg > 1) return (EINVAL); if (!sc->fan_handle) { /* Read the current fanstatus */ ACPI_EC_READ(sc->ec_dev, IBM_EC_FANSTATUS, &val_ec, 1); return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_FANSTATUS, (arg == 1) ? (val_ec | IBM_EC_MASK_FANSTATUS) : (val_ec & (~IBM_EC_MASK_FANSTATUS)), 1); } break; } return (0); } static int acpi_ibm_sysctl_init(struct acpi_ibm_softc *sc, int method) { int dummy; ACPI_OBJECT_TYPE cmos_t; ACPI_HANDLE ledb_handle; switch (method) { case ACPI_IBM_METHOD_EVENTS: return (TRUE); case ACPI_IBM_METHOD_EVENTMASK: return (sc->events_mask_supported); case ACPI_IBM_METHOD_HOTKEY: case ACPI_IBM_METHOD_BRIGHTNESS: case ACPI_IBM_METHOD_VOLUME: case ACPI_IBM_METHOD_MUTE: /* EC is required here, which was already checked before */ return (TRUE); case ACPI_IBM_METHOD_MIC_LED: if (ACPI_SUCCESS(AcpiGetHandle(sc->handle, "MMTS", &sc->mic_led_handle))) { /* Turn off mic led by default */ acpi_ibm_mic_led_set (sc, 0); return(TRUE); } else sc->mic_led_handle = NULL; return (FALSE); case ACPI_IBM_METHOD_THINKLIGHT: sc->cmos_handle = NULL; sc->light_get_supported = ACPI_SUCCESS(acpi_GetInteger( sc->ec_handle, IBM_NAME_KEYLIGHT, &sc->light_val)); if ((ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\UCMS", &sc->light_handle)) || ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\CMOS", &sc->light_handle)) || ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\CMS", &sc->light_handle))) && ACPI_SUCCESS(AcpiGetType(sc->light_handle, &cmos_t)) && cmos_t == ACPI_TYPE_METHOD) { sc->light_cmd_on = 0x0c; sc->light_cmd_off = 0x0d; sc->cmos_handle = sc->light_handle; } else if (ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\LGHT", &sc->light_handle))) { sc->light_cmd_on = 1; sc->light_cmd_off = 0; } else sc->light_handle = NULL; sc->light_set_supported = (sc->light_handle && ACPI_FAILURE(AcpiGetHandle(sc->ec_handle, "LEDB", &ledb_handle))); if (sc->light_get_supported) return (TRUE); if (sc->light_set_supported) { sc->light_val = 0; return (TRUE); } return (FALSE); case ACPI_IBM_METHOD_BLUETOOTH: case ACPI_IBM_METHOD_WLAN: if (ACPI_SUCCESS(acpi_GetInteger(sc->handle, IBM_NAME_WLAN_BT_GET, &dummy))) return (TRUE); return (FALSE); case ACPI_IBM_METHOD_FANSPEED: /* * Some models report the fan speed in levels from 0-7 * Newer models report it contiguously */ sc->fan_levels = (ACPI_SUCCESS(AcpiGetHandle(sc->handle, "GFAN", &sc->fan_handle)) || ACPI_SUCCESS(AcpiGetHandle(sc->handle, "\\FSPD", &sc->fan_handle))); return (TRUE); case ACPI_IBM_METHOD_FANLEVEL: case ACPI_IBM_METHOD_FANSTATUS: /* * Fan status is only supported on those models, * which report fan RPM contiguously, not in levels */ if (sc->fan_levels) return (FALSE); return (TRUE); case ACPI_IBM_METHOD_THERMAL: if (ACPI_SUCCESS(acpi_GetInteger(sc->ec_handle, IBM_NAME_THERMAL_GET, &dummy))) { sc->thermal_updt_supported = ACPI_SUCCESS(acpi_GetInteger(sc->ec_handle, IBM_NAME_THERMAL_UPDT, &dummy)); return (TRUE); } return (FALSE); case ACPI_IBM_METHOD_HANDLEREVENTS: return (TRUE); } return (FALSE); } static int acpi_ibm_thermal_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_ibm_softc *sc; int error = 0; char temp_cmd[] = "TMP0"; int temp[8]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_ibm_softc *)oidp->oid_arg1; ACPI_SERIAL_BEGIN(ibm); for (int i = 0; i < 8; ++i) { temp_cmd[3] = '0' + i; /* * The TMPx methods seem to return +/- 128 or 0 * when the respecting sensor is not available */ if (ACPI_FAILURE(acpi_GetInteger(sc->ec_handle, temp_cmd, &temp[i])) || ABS(temp[i]) == 128 || temp[i] == 0) temp[i] = -1; else if (sc->thermal_updt_supported) /* Temperature is reported in tenth of Kelvin */ temp[i] = (temp[i] - 2731 + 5) / 10; } error = sysctl_handle_opaque(oidp, &temp, 8*sizeof(int), req); ACPI_SERIAL_END(ibm); return (error); } static int acpi_ibm_handlerevents_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_ibm_softc *sc; int error = 0; struct sbuf sb; char *cp, *ep; int l, val; unsigned int handler_events; char temp[128]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_ibm_softc *)oidp->oid_arg1; if (sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND) == NULL) return (ENOMEM); ACPI_SERIAL_BEGIN(ibm); /* Get old values if this is a get request. */ if (req->newptr == NULL) { for (int i = 0; i < 8 * sizeof(sc->handler_events); i++) if (sc->handler_events & (1 << i)) sbuf_printf(&sb, "0x%02x ", i + 1); if (sbuf_len(&sb) == 0) sbuf_printf(&sb, "NONE"); } sbuf_trim(&sb); sbuf_finish(&sb); strlcpy(temp, sbuf_data(&sb), sizeof(temp)); sbuf_delete(&sb); error = sysctl_handle_string(oidp, temp, sizeof(temp), req); /* Check for error or no change */ if (error != 0 || req->newptr == NULL) goto out; /* If the user is setting a string, parse it. */ handler_events = 0; cp = temp; while (*cp) { if (isspace(*cp)) { cp++; continue; } ep = cp; while (*ep && !isspace(*ep)) ep++; l = ep - cp; if (l == 0) break; if (strncmp(cp, "NONE", 4) == 0) { cp = ep; continue; } if (l >= 3 && cp[0] == '0' && (cp[1] == 'X' || cp[1] == 'x')) val = strtoul(cp, &ep, 16); else val = strtoul(cp, &ep, 10); if (val == 0 || ep == cp || val >= 8 * sizeof(handler_events)) { cp[l] = '\0'; device_printf(sc->dev, "invalid event code: %s\n", cp); error = EINVAL; goto out; } handler_events |= 1 << (val - 1); cp = ep; } sc->handler_events = handler_events; out: ACPI_SERIAL_END(ibm); return (error); } static int acpi_ibm_brightness_set(struct acpi_ibm_softc *sc, int arg) { int val, step; UINT64 val_ec; ACPI_OBJECT Arg; ACPI_OBJECT_LIST Args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 7) return (EINVAL); /* Read the current brightness */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_BRIGHTNESS, &val_ec, 1); if (ACPI_FAILURE(status)) return (status); if (sc->cmos_handle) { val = val_ec & IBM_EC_MASK_BRI; Args.Count = 1; Args.Pointer = &Arg; Arg.Type = ACPI_TYPE_INTEGER; Arg.Integer.Value = (arg > val) ? IBM_CMOS_BRIGHTNESS_UP : IBM_CMOS_BRIGHTNESS_DOWN; step = (arg > val) ? 1 : -1; for (int i = val; i != arg; i += step) { status = AcpiEvaluateObject(sc->cmos_handle, NULL, &Args, NULL); if (ACPI_FAILURE(status)) { /* Record the last value */ if (i != val) { ACPI_EC_WRITE(sc->ec_dev, IBM_EC_BRIGHTNESS, i - step, 1); } return (status); } } } return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_BRIGHTNESS, arg, 1); } static int acpi_ibm_bluetooth_set(struct acpi_ibm_softc *sc, int arg) { int val; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 1) return (EINVAL); val = (arg == 1) ? sc->wlan_bt_flags | IBM_NAME_MASK_BT : sc->wlan_bt_flags & (~IBM_NAME_MASK_BT); return acpi_SetInteger(sc->handle, IBM_NAME_WLAN_BT_SET, val); } static int acpi_ibm_thinklight_set(struct acpi_ibm_softc *sc, int arg) { ACPI_OBJECT Arg; ACPI_OBJECT_LIST Args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 1) return (EINVAL); if (sc->light_set_supported) { Args.Count = 1; Args.Pointer = &Arg; Arg.Type = ACPI_TYPE_INTEGER; Arg.Integer.Value = arg ? sc->light_cmd_on : sc->light_cmd_off; status = AcpiEvaluateObject(sc->light_handle, NULL, &Args, NULL); if (ACPI_SUCCESS(status)) sc->light_val = arg; return (status); } return (0); } static int acpi_ibm_volume_set(struct acpi_ibm_softc *sc, int arg) { int val, step; UINT64 val_ec; ACPI_OBJECT Arg; ACPI_OBJECT_LIST Args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 14) return (EINVAL); /* Read the current volume */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); if (ACPI_FAILURE(status)) return (status); if (sc->cmos_handle) { val = val_ec & IBM_EC_MASK_VOL; Args.Count = 1; Args.Pointer = &Arg; Arg.Type = ACPI_TYPE_INTEGER; Arg.Integer.Value = (arg > val) ? IBM_CMOS_VOLUME_UP : IBM_CMOS_VOLUME_DOWN; step = (arg > val) ? 1 : -1; for (int i = val; i != arg; i += step) { status = AcpiEvaluateObject(sc->cmos_handle, NULL, &Args, NULL); if (ACPI_FAILURE(status)) { /* Record the last value */ if (i != val) { val_ec = i - step + (val_ec & (~IBM_EC_MASK_VOL)); ACPI_EC_WRITE(sc->ec_dev, IBM_EC_VOLUME, val_ec, 1); } return (status); } } } val_ec = arg + (val_ec & (~IBM_EC_MASK_VOL)); return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_VOLUME, val_ec, 1); } static int acpi_ibm_mute_set(struct acpi_ibm_softc *sc, int arg) { UINT64 val_ec; ACPI_OBJECT Arg; ACPI_OBJECT_LIST Args; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(ibm); if (arg < 0 || arg > 1) return (EINVAL); status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); if (ACPI_FAILURE(status)) return (status); if (sc->cmos_handle) { Args.Count = 1; Args.Pointer = &Arg; Arg.Type = ACPI_TYPE_INTEGER; Arg.Integer.Value = IBM_CMOS_VOLUME_MUTE; status = AcpiEvaluateObject(sc->cmos_handle, NULL, &Args, NULL); if (ACPI_FAILURE(status)) return (status); } val_ec = (arg == 1) ? val_ec | IBM_EC_MASK_MUTE : val_ec & (~IBM_EC_MASK_MUTE); return ACPI_EC_WRITE(sc->ec_dev, IBM_EC_VOLUME, val_ec, 1); } static void acpi_ibm_eventhandler(struct acpi_ibm_softc *sc, int arg) { int val; UINT64 val_ec; ACPI_STATUS status; ACPI_SERIAL_BEGIN(ibm); switch (arg) { case IBM_EVENT_SUSPEND_TO_RAM: power_pm_suspend(POWER_SLEEP_STATE_SUSPEND); break; case IBM_EVENT_BLUETOOTH: acpi_ibm_bluetooth_set(sc, (sc->wlan_bt_flags == 0)); break; case IBM_EVENT_BRIGHTNESS_UP: case IBM_EVENT_BRIGHTNESS_DOWN: /* Read the current brightness */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_BRIGHTNESS, &val_ec, 1); if (ACPI_FAILURE(status)) return; val = val_ec & IBM_EC_MASK_BRI; val = (arg == IBM_EVENT_BRIGHTNESS_UP) ? val + 1 : val - 1; acpi_ibm_brightness_set(sc, val); break; case IBM_EVENT_THINKLIGHT: acpi_ibm_thinklight_set(sc, (sc->light_val == 0)); break; case IBM_EVENT_VOLUME_UP: case IBM_EVENT_VOLUME_DOWN: /* Read the current volume */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); if (ACPI_FAILURE(status)) return; val = val_ec & IBM_EC_MASK_VOL; val = (arg == IBM_EVENT_VOLUME_UP) ? val + 1 : val - 1; acpi_ibm_volume_set(sc, val); break; case IBM_EVENT_MUTE: /* Read the current value */ status = ACPI_EC_READ(sc->ec_dev, IBM_EC_VOLUME, &val_ec, 1); if (ACPI_FAILURE(status)) return; val = ((val_ec & IBM_EC_MASK_MUTE) == IBM_EC_MASK_MUTE); acpi_ibm_mute_set(sc, (val == 0)); break; default: break; } ACPI_SERIAL_END(ibm); } static void acpi_ibm_notify(ACPI_HANDLE h, UINT32 notify, void *context) { int event, arg, type; device_t dev = context; struct acpi_ibm_softc *sc = device_get_softc(dev); ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); if (notify != 0x80) device_printf(dev, "Unknown notify\n"); for (;;) { acpi_GetInteger(acpi_get_handle(dev), IBM_NAME_EVENTS_GET, &event); if (event == 0) break; type = (event >> 12) & 0xf; arg = event & 0xfff; switch (type) { case 1: if (!(sc->events_availmask & (1 << (arg - 1)))) { device_printf(dev, "Unknown key %d\n", arg); break; } /* Execute event handler */ if (sc->handler_events & (1 << (arg - 1))) acpi_ibm_eventhandler(sc, (arg & 0xff)); /* Notify devd(8) */ acpi_UserNotify("IBM", h, (arg & 0xff)); break; default: break; } } } Index: head/sys/dev/acpi_support/acpi_panasonic.c =================================================================== --- head/sys/dev/acpi_support/acpi_panasonic.c (revision 339753) +++ head/sys/dev/acpi_support/acpi_panasonic.c (revision 339754) @@ -1,519 +1,521 @@ /*- * Copyright (c) 2003 OGAWA Takaya * Copyright (c) 2004 TAKAHASHI Yoshihiro * 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. * */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("Panasonic") /* Debug */ #undef ACPI_PANASONIC_DEBUG /* Operations */ #define HKEY_SET 0 #define HKEY_GET 1 /* Functions */ #define HKEY_REG_LCD_BRIGHTNESS_MAX_AC 0x02 #define HKEY_REG_LCD_BRIGHTNESS_MIN_AC 0x03 #define HKEY_REG_LCD_BRIGHTNESS_AC 0x04 #define HKEY_REG_LCD_BRIGHTNESS_MAX_DC 0x05 #define HKEY_REG_LCD_BRIGHTNESS_MIN_DC 0x06 #define HKEY_REG_LCD_BRIGHTNESS_DC 0x07 #define HKEY_REG_SOUND_MUTE 0x08 /* Field definitions */ #define HKEY_LCD_BRIGHTNESS_BITS 4 #define HKEY_LCD_BRIGHTNESS_DIV ((1 << HKEY_LCD_BRIGHTNESS_BITS) - 1) struct acpi_panasonic_softc { device_t dev; ACPI_HANDLE handle; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; eventhandler_tag power_evh; }; /* Prototype for HKEY functions for getting/setting a value. */ typedef int hkey_fn_t(ACPI_HANDLE, int, UINT32 *); static int acpi_panasonic_probe(device_t dev); static int acpi_panasonic_attach(device_t dev); static int acpi_panasonic_detach(device_t dev); static int acpi_panasonic_shutdown(device_t dev); static int acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS); static UINT64 acpi_panasonic_sinf(ACPI_HANDLE h, UINT64 index); static void acpi_panasonic_sset(ACPI_HANDLE h, UINT64 index, UINT64 val); static int acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, UINT32 *arg); static void acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, UINT32 key); static void acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, void *context); static void acpi_panasonic_power_profile(void *arg); static hkey_fn_t hkey_lcd_brightness_max; static hkey_fn_t hkey_lcd_brightness_min; static hkey_fn_t hkey_lcd_brightness; static hkey_fn_t hkey_sound_mute; ACPI_SERIAL_DECL(panasonic, "ACPI Panasonic extras"); /* Table of sysctl names and HKEY functions to call. */ static struct { char *name; hkey_fn_t *handler; } sysctl_table[] = { /* name, handler */ {"lcd_brightness_max", hkey_lcd_brightness_max}, {"lcd_brightness_min", hkey_lcd_brightness_min}, {"lcd_brightness", hkey_lcd_brightness}, {"sound_mute", hkey_sound_mute}, {NULL, NULL} }; static device_method_t acpi_panasonic_methods[] = { DEVMETHOD(device_probe, acpi_panasonic_probe), DEVMETHOD(device_attach, acpi_panasonic_attach), DEVMETHOD(device_detach, acpi_panasonic_detach), DEVMETHOD(device_shutdown, acpi_panasonic_shutdown), DEVMETHOD_END }; static driver_t acpi_panasonic_driver = { "acpi_panasonic", acpi_panasonic_methods, sizeof(struct acpi_panasonic_softc), }; static devclass_t acpi_panasonic_devclass; DRIVER_MODULE(acpi_panasonic, acpi, acpi_panasonic_driver, acpi_panasonic_devclass, 0, 0); MODULE_DEPEND(acpi_panasonic, acpi, 1, 1, 1); static int acpi_panasonic_probe(device_t dev) { static char *mat_ids[] = { "MAT0019", NULL }; - + int rv; + if (acpi_disabled("panasonic") || - ACPI_ID_PROBE(device_get_parent(dev), dev, mat_ids) == NULL || device_get_unit(dev) != 0) return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, mat_ids, NULL); - device_set_desc(dev, "Panasonic Notebook Hotkeys"); - return (0); + if (rv <= 0) + device_set_desc(dev, "Panasonic Notebook Hotkeys"); + return (rv); } static int acpi_panasonic_attach(device_t dev) { struct acpi_panasonic_softc *sc; struct acpi_softc *acpi_sc; ACPI_STATUS status; int i; sc = device_get_softc(dev); sc->dev = dev; sc->handle = acpi_get_handle(dev); acpi_sc = acpi_device_get_parent_softc(dev); /* Build sysctl tree */ sysctl_ctx_init(&sc->sysctl_ctx); sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "panasonic", CTLFLAG_RD, 0, ""); for (i = 0; sysctl_table[i].name != NULL; i++) { SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, sysctl_table[i].name, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, i, acpi_panasonic_sysctl, "I", ""); } #if 0 /* Activate hotkeys */ status = AcpiEvaluateObject(sc->handle, "", NULL, NULL); if (ACPI_FAILURE(status)) { device_printf(dev, "enable FN keys failed\n"); sysctl_ctx_free(&sc->sysctl_ctx); return (ENXIO); } #endif /* Handle notifies */ status = AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_panasonic_notify, sc); if (ACPI_FAILURE(status)) { device_printf(dev, "couldn't install notify handler - %s\n", AcpiFormatException(status)); sysctl_ctx_free(&sc->sysctl_ctx); return (ENXIO); } /* Install power profile event handler */ sc->power_evh = EVENTHANDLER_REGISTER(power_profile_change, acpi_panasonic_power_profile, sc->handle, 0); return (0); } static int acpi_panasonic_detach(device_t dev) { struct acpi_panasonic_softc *sc; sc = device_get_softc(dev); /* Remove power profile event handler */ EVENTHANDLER_DEREGISTER(power_profile_change, sc->power_evh); /* Remove notify handler */ AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_panasonic_notify); /* Free sysctl tree */ sysctl_ctx_free(&sc->sysctl_ctx); return (0); } static int acpi_panasonic_shutdown(device_t dev) { struct acpi_panasonic_softc *sc; int mute; /* Mute the main audio during reboot to prevent static burst to speaker. */ sc = device_get_softc(dev); mute = 1; hkey_sound_mute(sc->handle, HKEY_SET, &mute); return (0); } static int acpi_panasonic_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_panasonic_softc *sc; UINT32 arg; int function, error; hkey_fn_t *handler; sc = (struct acpi_panasonic_softc *)oidp->oid_arg1; function = oidp->oid_arg2; handler = sysctl_table[function].handler; /* Get the current value from the appropriate function. */ ACPI_SERIAL_BEGIN(panasonic); error = handler(sc->handle, HKEY_GET, &arg); if (error != 0) goto out; /* Send the current value to the user and return if no new value. */ error = sysctl_handle_int(oidp, &arg, 0, req); if (error != 0 || req->newptr == NULL) goto out; /* Set the new value via the appropriate function. */ error = handler(sc->handle, HKEY_SET, &arg); out: ACPI_SERIAL_END(panasonic); return (error); } static UINT64 acpi_panasonic_sinf(ACPI_HANDLE h, UINT64 index) { ACPI_BUFFER buf; ACPI_OBJECT *res; UINT64 ret; ACPI_SERIAL_ASSERT(panasonic); ret = -1; buf.Length = ACPI_ALLOCATE_BUFFER; buf.Pointer = NULL; AcpiEvaluateObject(h, "SINF", NULL, &buf); res = (ACPI_OBJECT *)buf.Pointer; if (res->Type == ACPI_TYPE_PACKAGE) ret = res->Package.Elements[index].Integer.Value; AcpiOsFree(buf.Pointer); return (ret); } static void acpi_panasonic_sset(ACPI_HANDLE h, UINT64 index, UINT64 val) { ACPI_OBJECT_LIST args; ACPI_OBJECT obj[2]; ACPI_SERIAL_ASSERT(panasonic); obj[0].Type = ACPI_TYPE_INTEGER; obj[0].Integer.Value = index; obj[1].Type = ACPI_TYPE_INTEGER; obj[1].Integer.Value = val; args.Count = 2; args.Pointer = obj; AcpiEvaluateObject(h, "SSET", &args, NULL); } static int hkey_lcd_brightness_max(ACPI_HANDLE h, int op, UINT32 *val) { int reg; ACPI_SERIAL_ASSERT(panasonic); reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? HKEY_REG_LCD_BRIGHTNESS_MAX_AC : HKEY_REG_LCD_BRIGHTNESS_MAX_DC; switch (op) { case HKEY_SET: return (EPERM); break; case HKEY_GET: *val = acpi_panasonic_sinf(h, reg); break; } return (0); } static int hkey_lcd_brightness_min(ACPI_HANDLE h, int op, UINT32 *val) { int reg; ACPI_SERIAL_ASSERT(panasonic); reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? HKEY_REG_LCD_BRIGHTNESS_MIN_AC : HKEY_REG_LCD_BRIGHTNESS_MIN_DC; switch (op) { case HKEY_SET: return (EPERM); break; case HKEY_GET: *val = acpi_panasonic_sinf(h, reg); break; } return (0); } static int hkey_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *val) { int reg; UINT32 max, min; reg = (power_profile_get_state() == POWER_PROFILE_PERFORMANCE) ? HKEY_REG_LCD_BRIGHTNESS_AC : HKEY_REG_LCD_BRIGHTNESS_DC; ACPI_SERIAL_ASSERT(panasonic); switch (op) { case HKEY_SET: hkey_lcd_brightness_max(h, HKEY_GET, &max); hkey_lcd_brightness_min(h, HKEY_GET, &min); if (*val < min || *val > max) return (EINVAL); acpi_panasonic_sset(h, reg, *val); break; case HKEY_GET: *val = acpi_panasonic_sinf(h, reg); break; } return (0); } static int hkey_sound_mute(ACPI_HANDLE h, int op, UINT32 *val) { ACPI_SERIAL_ASSERT(panasonic); switch (op) { case HKEY_SET: if (*val != 0 && *val != 1) return (EINVAL); acpi_panasonic_sset(h, HKEY_REG_SOUND_MUTE, *val); break; case HKEY_GET: *val = acpi_panasonic_sinf(h, HKEY_REG_SOUND_MUTE); break; } return (0); } static int acpi_panasonic_hkey_event(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, UINT32 *arg) { ACPI_BUFFER buf; ACPI_OBJECT *res; UINT64 val; int status; ACPI_SERIAL_ASSERT(panasonic); status = ENXIO; buf.Length = ACPI_ALLOCATE_BUFFER; buf.Pointer = NULL; AcpiEvaluateObject(h, "HINF", NULL, &buf); res = (ACPI_OBJECT *)buf.Pointer; if (res->Type != ACPI_TYPE_INTEGER) { device_printf(sc->dev, "HINF returned non-integer\n"); goto end; } val = res->Integer.Value; #ifdef ACPI_PANASONIC_DEBUG device_printf(sc->dev, "%s button Fn+F%d\n", (val & 0x80) ? "Pressed" : "Released", (int)(val & 0x7f)); #endif if ((val & 0x7f) > 0 && (val & 0x7f) < 11) { *arg = val; status = 0; } end: if (buf.Pointer) AcpiOsFree(buf.Pointer); return (status); } static void acpi_panasonic_hkey_action(struct acpi_panasonic_softc *sc, ACPI_HANDLE h, UINT32 key) { struct acpi_softc *acpi_sc; int arg, max, min; acpi_sc = acpi_device_get_parent_softc(sc->dev); ACPI_SERIAL_ASSERT(panasonic); switch (key) { case 1: /* Decrease LCD brightness. */ hkey_lcd_brightness_max(h, HKEY_GET, &max); hkey_lcd_brightness_min(h, HKEY_GET, &min); hkey_lcd_brightness(h, HKEY_GET, &arg); arg -= max / HKEY_LCD_BRIGHTNESS_DIV; if (arg < min) arg = min; else if (arg > max) arg = max; hkey_lcd_brightness(h, HKEY_SET, &arg); break; case 2: /* Increase LCD brightness. */ hkey_lcd_brightness_max(h, HKEY_GET, &max); hkey_lcd_brightness_min(h, HKEY_GET, &min); hkey_lcd_brightness(h, HKEY_GET, &arg); arg += max / HKEY_LCD_BRIGHTNESS_DIV; if (arg < min) arg = min; else if (arg > max) arg = max; hkey_lcd_brightness(h, HKEY_SET, &arg); break; case 4: /* Toggle sound mute. */ hkey_sound_mute(h, HKEY_GET, &arg); if (arg) arg = 0; else arg = 1; hkey_sound_mute(h, HKEY_SET, &arg); break; case 7: /* Suspend. */ acpi_event_sleep_button_sleep(acpi_sc); break; } } static void acpi_panasonic_notify(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_panasonic_softc *sc; UINT32 key = 0; sc = (struct acpi_panasonic_softc *)context; switch (notify) { case 0x80: ACPI_SERIAL_BEGIN(panasonic); if (acpi_panasonic_hkey_event(sc, h, &key) == 0) { acpi_panasonic_hkey_action(sc, h, key); acpi_UserNotify("Panasonic", h, (uint8_t)key); } ACPI_SERIAL_END(panasonic); break; case 0x81: if (!bootverbose) break; /* FALLTHROUGH */ default: device_printf(sc->dev, "unknown notify: %#x\n", notify); break; } } static void acpi_panasonic_power_profile(void *arg) { ACPI_HANDLE handle; UINT32 brightness; handle = (ACPI_HANDLE)arg; /* Reset current brightness according to new power state. */ ACPI_SERIAL_BEGIN(panasonic); hkey_lcd_brightness(handle, HKEY_GET, &brightness); hkey_lcd_brightness(handle, HKEY_SET, &brightness); ACPI_SERIAL_END(panasonic); } Index: head/sys/dev/acpi_support/acpi_rapidstart.c =================================================================== --- head/sys/dev/acpi_support/acpi_rapidstart.c (revision 339753) +++ head/sys/dev/acpi_support/acpi_rapidstart.c (revision 339754) @@ -1,142 +1,144 @@ /*- * Copyright (c) 2013 Takanori Watanabe * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include "acpi_if.h" #include #include #include static int sysctl_acpi_rapidstart_gen_handler(SYSCTL_HANDLER_ARGS); static struct acpi_rapidstart_name_list { char *nodename; char *getmethod; char *setmethod; char *comment; } acpi_rapidstart_oids[] ={ {"ffs","GFFS","SFFS","Flash Fast Store Flag"}, {"ftv","GFTV","SFTV","Time value"}, {NULL, NULL, NULL, NULL} }; struct acpi_rapidstart_softc { struct sysctl_ctx_list *sysctl_ctx; struct sysctl_oid *sysctl_tree; }; static char *rapidstart_ids[] = {"INT3392", NULL}; static int acpi_rapidstart_probe(device_t dev) { + int rv; + if (acpi_disabled("rapidstart") || - ACPI_ID_PROBE(device_get_parent(dev), dev, rapidstart_ids) == NULL || device_get_unit(dev) != 0) return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, rapidstart_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "Intel Rapid Start ACPI device"); - device_set_desc(dev, "Intel Rapid Start ACPI device"); - - return (0); + return (rv); } static int acpi_rapidstart_attach(device_t dev) { struct acpi_rapidstart_softc *sc; int i; sc = device_get_softc(dev); sc->sysctl_ctx = device_get_sysctl_ctx(dev); sc->sysctl_tree = device_get_sysctl_tree(dev); for (i = 0 ; acpi_rapidstart_oids[i].nodename != NULL; i++){ if (acpi_rapidstart_oids[i].setmethod != NULL) { SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), i, acpi_rapidstart_oids[i].nodename, CTLTYPE_INT | CTLFLAG_RW, dev, i, sysctl_acpi_rapidstart_gen_handler, "I", acpi_rapidstart_oids[i].comment); } else { SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), i, acpi_rapidstart_oids[i].nodename, CTLTYPE_INT | CTLFLAG_RD, dev, i, sysctl_acpi_rapidstart_gen_handler, "I", acpi_rapidstart_oids[i].comment); } } return (0); } static int sysctl_acpi_rapidstart_gen_handler(SYSCTL_HANDLER_ARGS) { device_t dev = arg1; int function = oidp->oid_arg2; int error = 0, val; acpi_GetInteger(acpi_get_handle(dev), acpi_rapidstart_oids[function].getmethod, &val); error = sysctl_handle_int(oidp, &val, 0, req); if (error || !req->newptr || !acpi_rapidstart_oids[function].setmethod) return (error); acpi_SetInteger(acpi_get_handle(dev), acpi_rapidstart_oids[function].setmethod, val); return (0); } static device_method_t acpi_rapidstart_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_rapidstart_probe), DEVMETHOD(device_attach, acpi_rapidstart_attach), DEVMETHOD_END }; static driver_t acpi_rapidstart_driver = { "acpi_rapidstart", acpi_rapidstart_methods, sizeof(struct acpi_rapidstart_softc), }; static devclass_t acpi_rapidstart_devclass; DRIVER_MODULE(acpi_rapidstart, acpi, acpi_rapidstart_driver, acpi_rapidstart_devclass, 0, 0); MODULE_DEPEND(acpi_rapidstart, acpi, 1, 1, 1); Index: head/sys/dev/acpi_support/acpi_sony.c =================================================================== --- head/sys/dev/acpi_support/acpi_sony.c (revision 339753) +++ head/sys/dev/acpi_support/acpi_sony.c (revision 339754) @@ -1,191 +1,191 @@ /*- * Copyright (c) 2004 Takanori Watanabe * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include "acpi_if.h" #include #include #include #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("Sony") #define ACPI_SONY_GET_PID "GPID" /* * SNY5001 * This is the ACPI handle for the "Sony Notebook Control" driver under * Windows. * It provides several methods within the ACPI namespace, including: * [GS]BRT [GS]PBR [GS]CTR [GS]PCR [GS]CMI [CDPW GCDP]? GWDP PWAK PWRN * * SNY6001 * This is the ACPI handle for the "Sony Programmable I/O" driver under * Windows. * It is not yet supported by this driver, but provides control over the * power to the bluetooth, built-in camera and HSDPA modem devices in some * laptops, and also allows some control of the fan speed. */ struct acpi_sony_softc { int pid; }; static struct acpi_sony_name_list { char *nodename; char *getmethod; char *setmethod; char *comment; } acpi_sony_oids[] = { { "brightness", "GBRT", "SBRT", "Display Brightness"}, { "brightness_default", "GPBR", "SPBR", "Default Display Brightness"}, { "contrast", "GCTR", "SCTR", "Display Contrast"}, { "bass_gain", "GMGB", "SMGB", "Multimedia Bass Gain"}, { "pcr", "GPCR", "SPCR", "???"}, #if 0 { "cmi", "GCMI", "SCMI", "???"}, #endif { "wdp", "GWDP", NULL, "???"}, { "cdp", "GCDP", "CDPW", "CD Power"}, /*shares [\GL03]&0x8 flag*/ { "azp", "GAZP", "AZPW", "Audio Power"}, { "lnp", "GLNP", "LNPW", "LAN Power"}, { NULL, NULL, NULL } }; static int acpi_sony_probe(device_t dev); static int acpi_sony_attach(device_t dev); static int acpi_sony_detach(device_t dev); static int sysctl_acpi_sony_gen_handler(SYSCTL_HANDLER_ARGS); static device_method_t acpi_sony_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_sony_probe), DEVMETHOD(device_attach, acpi_sony_attach), DEVMETHOD(device_detach, acpi_sony_detach), DEVMETHOD_END }; static driver_t acpi_sony_driver = { "acpi_sony", acpi_sony_methods, sizeof(struct acpi_sony_softc), }; static devclass_t acpi_sony_devclass; DRIVER_MODULE(acpi_sony, acpi, acpi_sony_driver, acpi_sony_devclass, 0, 0); MODULE_DEPEND(acpi_sony, acpi, 1, 1, 1); static char *sny_id[] = {"SNY5001", NULL}; static int acpi_sony_probe(device_t dev) { - int ret = ENXIO; + int ret; - if (ACPI_ID_PROBE(device_get_parent(dev), dev, sny_id)) { + ret = ACPI_ID_PROBE(device_get_parent(dev), dev, sny_id, NULL); + if (ret <= 0) { device_set_desc(dev, "Sony notebook controller"); - ret = 0; } return (ret); } static int acpi_sony_attach(device_t dev) { struct acpi_sony_softc *sc; int i; sc = device_get_softc(dev); acpi_GetInteger(acpi_get_handle(dev), ACPI_SONY_GET_PID, &sc->pid); device_printf(dev, "PID %x\n", sc->pid); for (i = 0 ; acpi_sony_oids[i].nodename != NULL; i++) { if (acpi_sony_oids[i].setmethod != NULL) { SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), i, acpi_sony_oids[i].nodename , CTLTYPE_INT | CTLFLAG_RW, dev, i, sysctl_acpi_sony_gen_handler, "I", acpi_sony_oids[i].comment); } else { SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), i, acpi_sony_oids[i].nodename , CTLTYPE_INT | CTLFLAG_RD, dev, i, sysctl_acpi_sony_gen_handler, "I", acpi_sony_oids[i].comment); } } return (0); } static int acpi_sony_detach(device_t dev) { return (0); } #if 0 static int acpi_sony_suspend(device_t dev) { struct acpi_sony_softc *sc = device_get_softc(dev); return (0); } static int acpi_sony_resume(device_t dev) { return (0); } #endif static int sysctl_acpi_sony_gen_handler(SYSCTL_HANDLER_ARGS) { device_t dev = arg1; int function = oidp->oid_arg2; int error = 0, val; acpi_GetInteger(acpi_get_handle(dev), acpi_sony_oids[function].getmethod, &val); error = sysctl_handle_int(oidp, &val, 0, req); if (error || !req->newptr || !acpi_sony_oids[function].setmethod) return (error); acpi_SetInteger(acpi_get_handle(dev), acpi_sony_oids[function].setmethod, val); return (0); } Index: head/sys/dev/acpi_support/acpi_toshiba.c =================================================================== --- head/sys/dev/acpi_support/acpi_toshiba.c (revision 339753) +++ head/sys/dev/acpi_support/acpi_toshiba.c (revision 339754) @@ -1,567 +1,570 @@ /*- * Copyright (c) 2003 Hiroyuki Aizu * 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. * */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("Toshiba") /* * Toshiba HCI interface definitions * * HCI is Toshiba's "Hardware Control Interface" which is supposed to * be uniform across all their models. Ideally we would just call * dedicated ACPI methods instead of using this primitive interface. * However, the ACPI methods seem to be incomplete in some areas (for * example they allow setting, but not reading, the LCD brightness * value), so this is still useful. */ #define METHOD_HCI "GHCI" #define METHOD_HCI_ENABLE "ENAB" #define METHOD_VIDEO "DSSX" /* Operations */ #define HCI_SET 0xFF00 #define HCI_GET 0xFE00 /* Return codes */ #define HCI_SUCCESS 0x0000 #define HCI_FAILURE 0x1000 #define HCI_NOT_SUPPORTED 0x8000 #define HCI_EMPTY 0x8C00 /* Functions */ #define HCI_REG_LCD_BACKLIGHT 0x0002 #define HCI_REG_FAN 0x0004 #define HCI_REG_SYSTEM_EVENT 0x0016 #define HCI_REG_VIDEO_OUTPUT 0x001C #define HCI_REG_HOTKEY_EVENT 0x001E #define HCI_REG_LCD_BRIGHTNESS 0x002A #define HCI_REG_CPU_SPEED 0x0032 /* Field definitions */ #define HCI_FAN_SHIFT 7 #define HCI_LCD_BRIGHTNESS_BITS 3 #define HCI_LCD_BRIGHTNESS_SHIFT (16 - HCI_LCD_BRIGHTNESS_BITS) #define HCI_LCD_BRIGHTNESS_MAX ((1 << HCI_LCD_BRIGHTNESS_BITS) - 1) #define HCI_VIDEO_OUTPUT_FLAG 0x0100 #define HCI_VIDEO_OUTPUT_LCD 0x1 #define HCI_VIDEO_OUTPUT_CRT 0x2 #define HCI_VIDEO_OUTPUT_TV 0x4 #define HCI_CPU_SPEED_BITS 3 #define HCI_CPU_SPEED_SHIFT (16 - HCI_CPU_SPEED_BITS) #define HCI_CPU_SPEED_MAX ((1 << HCI_CPU_SPEED_BITS) - 1) /* Key press/release events. */ #define FN_F1_PRESS 0x013B #define FN_F1_RELEASE 0x01BB #define FN_F2_PRESS 0x013C #define FN_F2_RELEASE 0x01BC #define FN_F3_PRESS 0x013D #define FN_F3_RELEASE 0x01BD #define FN_F4_PRESS 0x013E #define FN_F4_RELEASE 0x01BE #define FN_F5_PRESS 0x013F #define FN_F5_RELEASE 0x01BF #define FN_F6_PRESS 0x0140 #define FN_F6_RELEASE 0x01C0 #define FN_F7_PRESS 0x0141 #define FN_F7_RELEASE 0x01C1 #define FN_F8_PRESS 0x0142 #define FN_F8_RELEASE 0x01C2 #define FN_F9_PRESS 0x0143 #define FN_F9_RELEASE 0x01C3 #define FN_BS_PRESS 0x010E #define FN_BS_RELEASE 0x018E #define FN_ESC_PRESS 0x0101 #define FN_ESC_RELEASE 0x0181 #define FN_KNJ_PRESS 0x0129 #define FN_KNJ_RELEASE 0x01A9 /* HCI register definitions. */ #define HCI_WORDS 6 /* Number of registers */ #define HCI_REG_AX 0 /* Operation, then return value */ #define HCI_REG_BX 1 /* Function */ #define HCI_REG_CX 2 /* Argument (in or out) */ #define HCI_REG_DX 3 /* Unused? */ #define HCI_REG_SI 4 /* Unused? */ #define HCI_REG_DI 5 /* Unused? */ struct acpi_toshiba_softc { device_t dev; ACPI_HANDLE handle; ACPI_HANDLE video_handle; struct sysctl_ctx_list sysctl_ctx; struct sysctl_oid *sysctl_tree; }; /* Prototype for HCI functions for getting/setting a value. */ typedef int hci_fn_t(ACPI_HANDLE, int, UINT32 *); static int acpi_toshiba_probe(device_t dev); static int acpi_toshiba_attach(device_t dev); static int acpi_toshiba_detach(device_t dev); static int acpi_toshiba_sysctl(SYSCTL_HANDLER_ARGS); static hci_fn_t hci_force_fan; static hci_fn_t hci_video_output; static hci_fn_t hci_lcd_brightness; static hci_fn_t hci_lcd_backlight; static hci_fn_t hci_cpu_speed; static int hci_call(ACPI_HANDLE h, int op, int function, UINT32 *arg); static void hci_key_action(struct acpi_toshiba_softc *sc, ACPI_HANDLE h, UINT32 key); static void acpi_toshiba_notify(ACPI_HANDLE h, UINT32 notify, void *context); static int acpi_toshiba_video_probe(device_t dev); static int acpi_toshiba_video_attach(device_t dev); ACPI_SERIAL_DECL(toshiba, "ACPI Toshiba Extras"); /* Table of sysctl names and HCI functions to call. */ static struct { char *name; hci_fn_t *handler; } sysctl_table[] = { /* name, handler */ {"force_fan", hci_force_fan}, {"video_output", hci_video_output}, {"lcd_brightness", hci_lcd_brightness}, {"lcd_backlight", hci_lcd_backlight}, {"cpu_speed", hci_cpu_speed}, {NULL, NULL} }; static device_method_t acpi_toshiba_methods[] = { DEVMETHOD(device_probe, acpi_toshiba_probe), DEVMETHOD(device_attach, acpi_toshiba_attach), DEVMETHOD(device_detach, acpi_toshiba_detach), DEVMETHOD_END }; static driver_t acpi_toshiba_driver = { "acpi_toshiba", acpi_toshiba_methods, sizeof(struct acpi_toshiba_softc), }; static devclass_t acpi_toshiba_devclass; DRIVER_MODULE(acpi_toshiba, acpi, acpi_toshiba_driver, acpi_toshiba_devclass, 0, 0); MODULE_DEPEND(acpi_toshiba, acpi, 1, 1, 1); static device_method_t acpi_toshiba_video_methods[] = { DEVMETHOD(device_probe, acpi_toshiba_video_probe), DEVMETHOD(device_attach, acpi_toshiba_video_attach), DEVMETHOD_END }; static driver_t acpi_toshiba_video_driver = { "acpi_toshiba_video", acpi_toshiba_video_methods, 0, }; static devclass_t acpi_toshiba_video_devclass; DRIVER_MODULE(acpi_toshiba_video, acpi, acpi_toshiba_video_driver, acpi_toshiba_video_devclass, 0, 0); MODULE_DEPEND(acpi_toshiba_video, acpi, 1, 1, 1); static int enable_fn_keys = 1; TUNABLE_INT("hw.acpi.toshiba.enable_fn_keys", &enable_fn_keys); /* * HID Model * ------------------------------------- * TOS6200 Libretto L Series * Dynabook Satellite 2455 * Dynabook SS 3500 * TOS6207 Dynabook SS2110 Series * TOS6208 SPA40 */ static int acpi_toshiba_probe(device_t dev) { static char *tosh_ids[] = { "TOS6200", "TOS6207", "TOS6208", NULL }; + int rv; if (acpi_disabled("toshiba") || - ACPI_ID_PROBE(device_get_parent(dev), dev, tosh_ids) == NULL || device_get_unit(dev) != 0) return (ENXIO); - - device_set_desc(dev, "Toshiba HCI Extras"); - return (0); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, tosh_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "Toshiba HCI Extras"); + return (rv); } static int acpi_toshiba_attach(device_t dev) { struct acpi_toshiba_softc *sc; struct acpi_softc *acpi_sc; ACPI_STATUS status; int i; sc = device_get_softc(dev); sc->dev = dev; sc->handle = acpi_get_handle(dev); acpi_sc = acpi_device_get_parent_softc(dev); sysctl_ctx_init(&sc->sysctl_ctx); sc->sysctl_tree = SYSCTL_ADD_NODE(&sc->sysctl_ctx, SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "toshiba", CTLFLAG_RD, 0, ""); for (i = 0; sysctl_table[i].name != NULL; i++) { SYSCTL_ADD_PROC(&sc->sysctl_ctx, SYSCTL_CHILDREN(sc->sysctl_tree), OID_AUTO, sysctl_table[i].name, CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, i, acpi_toshiba_sysctl, "I", ""); } if (enable_fn_keys != 0) { status = AcpiEvaluateObject(sc->handle, METHOD_HCI_ENABLE, NULL, NULL); if (ACPI_FAILURE(status)) { device_printf(dev, "enable FN keys failed\n"); sysctl_ctx_free(&sc->sysctl_ctx); return (ENXIO); } AcpiInstallNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_toshiba_notify, sc); } return (0); } static int acpi_toshiba_detach(device_t dev) { struct acpi_toshiba_softc *sc; sc = device_get_softc(dev); if (enable_fn_keys != 0) { AcpiRemoveNotifyHandler(sc->handle, ACPI_DEVICE_NOTIFY, acpi_toshiba_notify); } sysctl_ctx_free(&sc->sysctl_ctx); return (0); } static int acpi_toshiba_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_toshiba_softc *sc; UINT32 arg; int function, error = 0; hci_fn_t *handler; sc = (struct acpi_toshiba_softc *)oidp->oid_arg1; function = oidp->oid_arg2; handler = sysctl_table[function].handler; /* Get the current value from the appropriate function. */ ACPI_SERIAL_BEGIN(toshiba); error = handler(sc->handle, HCI_GET, &arg); if (error != 0) goto out; /* Send the current value to the user and return if no new value. */ error = sysctl_handle_int(oidp, &arg, 0, req); if (error != 0 || req->newptr == NULL) goto out; /* Set the new value via the appropriate function. */ error = handler(sc->handle, HCI_SET, &arg); out: ACPI_SERIAL_END(toshiba); return (error); } static int hci_force_fan(ACPI_HANDLE h, int op, UINT32 *state) { int ret; ACPI_SERIAL_ASSERT(toshiba); if (op == HCI_SET) { if (*state > 1) return (EINVAL); *state <<= HCI_FAN_SHIFT; } ret = hci_call(h, op, HCI_REG_FAN, state); if (ret == 0 && op == HCI_GET) *state >>= HCI_FAN_SHIFT; return (ret); } static int hci_video_output(ACPI_HANDLE h, int op, UINT32 *video_output) { int ret; ACPI_STATUS status; ACPI_SERIAL_ASSERT(toshiba); if (op == HCI_SET) { if (*video_output < 1 || *video_output > 7) return (EINVAL); if (h == NULL) return (ENXIO); *video_output |= HCI_VIDEO_OUTPUT_FLAG; status = acpi_SetInteger(h, METHOD_VIDEO, *video_output); if (ACPI_SUCCESS(status)) ret = 0; else ret = ENXIO; } else { ret = hci_call(h, op, HCI_REG_VIDEO_OUTPUT, video_output); if (ret == 0) *video_output &= 0xff; } return (ret); } static int hci_lcd_brightness(ACPI_HANDLE h, int op, UINT32 *brightness) { int ret; ACPI_SERIAL_ASSERT(toshiba); if (op == HCI_SET) { if (*brightness > HCI_LCD_BRIGHTNESS_MAX) return (EINVAL); *brightness <<= HCI_LCD_BRIGHTNESS_SHIFT; } ret = hci_call(h, op, HCI_REG_LCD_BRIGHTNESS, brightness); if (ret == 0 && op == HCI_GET) *brightness >>= HCI_LCD_BRIGHTNESS_SHIFT; return (ret); } static int hci_lcd_backlight(ACPI_HANDLE h, int op, UINT32 *backlight) { ACPI_SERIAL_ASSERT(toshiba); if (op == HCI_SET) { if (*backlight > 1) return (EINVAL); } return (hci_call(h, op, HCI_REG_LCD_BACKLIGHT, backlight)); } static int hci_cpu_speed(ACPI_HANDLE h, int op, UINT32 *speed) { int ret; ACPI_SERIAL_ASSERT(toshiba); if (op == HCI_SET) { if (*speed > HCI_CPU_SPEED_MAX) return (EINVAL); *speed <<= HCI_CPU_SPEED_SHIFT; } ret = hci_call(h, op, HCI_REG_CPU_SPEED, speed); if (ret == 0 && op == HCI_GET) *speed >>= HCI_CPU_SPEED_SHIFT; return (ret); } static int hci_call(ACPI_HANDLE h, int op, int function, UINT32 *arg) { ACPI_OBJECT_LIST args; ACPI_BUFFER results; ACPI_OBJECT obj[HCI_WORDS]; ACPI_OBJECT *res; int status, i, ret; ACPI_SERIAL_ASSERT(toshiba); status = ENXIO; for (i = 0; i < HCI_WORDS; i++) { obj[i].Type = ACPI_TYPE_INTEGER; obj[i].Integer.Value = 0; } obj[HCI_REG_AX].Integer.Value = op; obj[HCI_REG_BX].Integer.Value = function; if (op == HCI_SET) obj[HCI_REG_CX].Integer.Value = *arg; args.Count = HCI_WORDS; args.Pointer = obj; results.Pointer = NULL; results.Length = ACPI_ALLOCATE_BUFFER; if (ACPI_FAILURE(AcpiEvaluateObject(h, METHOD_HCI, &args, &results))) goto end; res = (ACPI_OBJECT *)results.Pointer; if (!ACPI_PKG_VALID(res, HCI_WORDS)) { printf("toshiba: invalid package!\n"); return (ENXIO); } acpi_PkgInt32(res, HCI_REG_AX, &ret); if (ret == HCI_SUCCESS) { if (op == HCI_GET) acpi_PkgInt32(res, HCI_REG_CX, arg); status = 0; } else if (function == HCI_REG_SYSTEM_EVENT && op == HCI_GET && ret == HCI_NOT_SUPPORTED) { /* * Sometimes system events are disabled without us requesting * it. This workaround attempts to re-enable them. * * XXX This call probably shouldn't be recursive. Queueing * a task via AcpiOsQueueForExecution() might be better. */ i = 1; hci_call(h, HCI_SET, HCI_REG_SYSTEM_EVENT, &i); } end: if (results.Pointer != NULL) AcpiOsFree(results.Pointer); return (status); } /* * Perform a few actions based on the keypress. Users can extend this * functionality by reading the keystrokes we send to devd(8). */ static void hci_key_action(struct acpi_toshiba_softc *sc, ACPI_HANDLE h, UINT32 key) { UINT32 arg; ACPI_SERIAL_ASSERT(toshiba); switch (key) { case FN_F6_RELEASE: /* Decrease LCD brightness. */ hci_lcd_brightness(h, HCI_GET, &arg); if (arg-- == 0) arg = 0; else hci_lcd_brightness(h, HCI_SET, &arg); break; case FN_F7_RELEASE: /* Increase LCD brightness. */ hci_lcd_brightness(h, HCI_GET, &arg); if (arg++ == 7) arg = 7; else hci_lcd_brightness(h, HCI_SET, &arg); break; case FN_F5_RELEASE: /* Cycle through video outputs. */ hci_video_output(h, HCI_GET, &arg); arg = (arg + 1) % 7; hci_video_output(sc->video_handle, HCI_SET, &arg); break; case FN_F8_RELEASE: /* Toggle LCD backlight. */ hci_lcd_backlight(h, HCI_GET, &arg); arg = (arg != 0) ? 0 : 1; hci_lcd_backlight(h, HCI_SET, &arg); break; case FN_ESC_RELEASE: /* Toggle forcing fan on. */ hci_force_fan(h, HCI_GET, &arg); arg = (arg != 0) ? 0 : 1; hci_force_fan(h, HCI_SET, &arg); break; } } static void acpi_toshiba_notify(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_toshiba_softc *sc; UINT32 key; sc = (struct acpi_toshiba_softc *)context; if (notify == 0x80) { ACPI_SERIAL_BEGIN(toshiba); while (hci_call(h, HCI_GET, HCI_REG_SYSTEM_EVENT, &key) == 0) { hci_key_action(sc, h, key); acpi_UserNotify("TOSHIBA", h, (uint8_t)key); } ACPI_SERIAL_END(toshiba); } else device_printf(sc->dev, "unknown notify: 0x%x\n", notify); } /* * Toshiba video pseudo-device to provide the DSSX method. * * HID Model * ------------------------------------- * TOS6201 Libretto L Series */ static int acpi_toshiba_video_probe(device_t dev) { static char *vid_ids[] = { "TOS6201", NULL }; + int rv; if (acpi_disabled("toshiba") || - ACPI_ID_PROBE(device_get_parent(dev), dev, vid_ids) == NULL || device_get_unit(dev) != 0) return (ENXIO); device_quiet(dev); - device_set_desc(dev, "Toshiba Video"); - return (0); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, vid_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "Toshiba Video"); + return (rv); } static int acpi_toshiba_video_attach(device_t dev) { struct acpi_toshiba_softc *sc; sc = devclass_get_softc(acpi_toshiba_devclass, 0); if (sc == NULL) return (ENXIO); sc->video_handle = acpi_get_handle(dev); return (0); } Index: head/sys/dev/acpi_support/acpi_wmi.c =================================================================== --- head/sys/dev/acpi_support/acpi_wmi.c (revision 339753) +++ head/sys/dev/acpi_support/acpi_wmi.c (revision 339754) @@ -1,1020 +1,1023 @@ /*- * Copyright (c) 2009 Michael Gmelin * 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. */ #include __FBSDID("$FreeBSD$"); /* * Driver for acpi-wmi mapping, provides an interface for vendor specific * implementations (e.g. HP and Acer laptops). * Inspired by the ACPI-WMI mapping driver (c) 2008-2008 Carlos Corbacho which * implements this functionality for Linux. * * WMI and ACPI: http://www.microsoft.com/whdc/system/pnppwr/wmi/wmi-acpi.mspx * acpi-wmi for Linux: http://www.kernel.org */ #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "acpi_wmi_if.h" static MALLOC_DEFINE(M_ACPIWMI, "acpiwmi", "ACPI-WMI mapping"); #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("ACPI_WMI"); #define ACPI_WMI_REGFLAG_EXPENSIVE 0x1 /* GUID flag: Expensive operation */ #define ACPI_WMI_REGFLAG_METHOD 0x2 /* GUID flag: Method call */ #define ACPI_WMI_REGFLAG_STRING 0x4 /* GUID flag: String */ #define ACPI_WMI_REGFLAG_EVENT 0x8 /* GUID flag: Event */ /* * acpi_wmi driver private structure */ struct acpi_wmi_softc { device_t wmi_dev; /* wmi device id */ ACPI_HANDLE wmi_handle; /* handle of the PNP0C14 node */ device_t ec_dev; /* acpi_ec0 */ struct cdev *wmistat_dev_t; /* wmistat device handle */ struct sbuf wmistat_sbuf; /* sbuf for /dev/wmistat output */ pid_t wmistat_open_pid; /* pid operating on /dev/wmistat */ int wmistat_bufptr; /* /dev/wmistat ptr to buffer position */ TAILQ_HEAD(wmi_info_list_head, wmi_info) wmi_info_list; }; /* * Struct that holds information about * about a single GUID entry in _WDG */ struct guid_info { char guid[16]; /* 16 byte non human readable GUID */ char oid[2]; /* object id or event notify id (first byte) */ UINT8 max_instance; /* highest instance known for this GUID */ UINT8 flags; /* ACPI_WMI_REGFLAG_%s */ }; /* WExx event generation state (on/off) */ enum event_generation_state { EVENT_GENERATION_ON = 1, EVENT_GENERATION_OFF = 0 }; /* * Information about one entry in _WDG. * List of those is used to lookup information by GUID. */ struct wmi_info { TAILQ_ENTRY(wmi_info) wmi_list; struct guid_info ginfo; /* information on guid */ ACPI_NOTIFY_HANDLER event_handler;/* client provided event handler */ void *event_handler_user_data; /* ev handler cookie */ }; ACPI_SERIAL_DECL(acpi_wmi, "ACPI-WMI Mapping"); /* public interface - declaration */ /* standard device interface*/ static int acpi_wmi_probe(device_t dev); static int acpi_wmi_attach(device_t dev); static int acpi_wmi_detach(device_t dev); /* see acpi_wmi_if.m */ static int acpi_wmi_provides_guid_string_method(device_t dev, const char *guid_string); static ACPI_STATUS acpi_wmi_evaluate_call_method(device_t dev, const char *guid_string, UINT8 instance, UINT32 method_id, const ACPI_BUFFER *in, ACPI_BUFFER *out); static ACPI_STATUS acpi_wmi_install_event_handler_method(device_t dev, const char *guid_string, ACPI_NOTIFY_HANDLER handler, void *data); static ACPI_STATUS acpi_wmi_remove_event_handler_method(device_t dev, const char *guid_string); static ACPI_STATUS acpi_wmi_get_event_data_method(device_t dev, UINT32 event_id, ACPI_BUFFER *out); static ACPI_STATUS acpi_wmi_get_block_method(device_t dev, const char *guid_string, UINT8 instance, ACPI_BUFFER *out); static ACPI_STATUS acpi_wmi_set_block_method(device_t dev, const char *guid_string, UINT8 instance, const ACPI_BUFFER *in); /* private interface - declaration */ /* callbacks */ static void acpi_wmi_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context); static ACPI_STATUS acpi_wmi_ec_handler(UINT32 function, ACPI_PHYSICAL_ADDRESS address, UINT32 width, UINT64 *value, void *context, void *region_context); /* helpers */ static ACPI_STATUS acpi_wmi_read_wdg_blocks(struct acpi_wmi_softc *sc, ACPI_HANDLE h); static ACPI_STATUS acpi_wmi_toggle_we_event_generation(device_t dev, struct wmi_info *winfo, enum event_generation_state state); static int acpi_wmi_guid_string_to_guid(const UINT8 *guid_string, UINT8 *guid); static struct wmi_info* acpi_wmi_lookup_wmi_info_by_guid_string(struct acpi_wmi_softc *sc, const char *guid_string); static d_open_t acpi_wmi_wmistat_open; static d_close_t acpi_wmi_wmistat_close; static d_read_t acpi_wmi_wmistat_read; /* handler /dev/wmistat device */ static struct cdevsw wmistat_cdevsw = { .d_version = D_VERSION, .d_open = acpi_wmi_wmistat_open, .d_close = acpi_wmi_wmistat_close, .d_read = acpi_wmi_wmistat_read, .d_name = "wmistat", }; static device_method_t acpi_wmi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_wmi_probe), DEVMETHOD(device_attach, acpi_wmi_attach), DEVMETHOD(device_detach, acpi_wmi_detach), /* bus interface */ DEVMETHOD(bus_add_child, bus_generic_add_child), /* acpi_wmi interface */ DEVMETHOD(acpi_wmi_provides_guid_string, acpi_wmi_provides_guid_string_method), DEVMETHOD(acpi_wmi_evaluate_call, acpi_wmi_evaluate_call_method), DEVMETHOD(acpi_wmi_install_event_handler, acpi_wmi_install_event_handler_method), DEVMETHOD(acpi_wmi_remove_event_handler, acpi_wmi_remove_event_handler_method), DEVMETHOD(acpi_wmi_get_event_data, acpi_wmi_get_event_data_method), DEVMETHOD(acpi_wmi_get_block, acpi_wmi_get_block_method), DEVMETHOD(acpi_wmi_set_block, acpi_wmi_set_block_method), DEVMETHOD_END }; static driver_t acpi_wmi_driver = { "acpi_wmi", acpi_wmi_methods, sizeof(struct acpi_wmi_softc), }; static devclass_t acpi_wmi_devclass; DRIVER_MODULE(acpi_wmi, acpi, acpi_wmi_driver, acpi_wmi_devclass, 0, 0); MODULE_VERSION(acpi_wmi, 1); MODULE_DEPEND(acpi_wmi, acpi, 1, 1, 1); static char *wmi_ids[] = {"PNP0C14", NULL}; /* * Probe for the PNP0C14 ACPI node */ static int acpi_wmi_probe(device_t dev) { - if (acpi_disabled("wmi") || - ACPI_ID_PROBE(device_get_parent(dev), dev, wmi_ids) == NULL) + int rv; + + if (acpi_disabled("wmi")) return (ENXIO); - device_set_desc(dev, "ACPI-WMI mapping"); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, wmi_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "ACPI-WMI mapping"); - return (0); + return (rv); } /* * Attach the device by: * - Looking for the first ACPI EC device * - Install the notify handler * - Install the EC address space handler * - Look for the _WDG node and read GUID information blocks */ static int acpi_wmi_attach(device_t dev) { struct acpi_wmi_softc *sc; int ret; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); ret = ENXIO; ACPI_SERIAL_BEGIN(acpi_wmi); sc->wmi_dev = dev; sc->wmi_handle = acpi_get_handle(dev); TAILQ_INIT(&sc->wmi_info_list); /* XXX Only works with one EC, but nearly all systems only have one. */ if ((sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0)) == NULL) device_printf(dev, "cannot find EC device\n"); else if (ACPI_FAILURE((status = AcpiInstallNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY, acpi_wmi_notify_handler, sc)))) device_printf(sc->wmi_dev, "couldn't install notify handler - %s\n", AcpiFormatException(status)); else if (ACPI_FAILURE((status = AcpiInstallAddressSpaceHandler( sc->wmi_handle, ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler, NULL, sc)))) { device_printf(sc->wmi_dev, "couldn't install EC handler - %s\n", AcpiFormatException(status)); AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY, acpi_wmi_notify_handler); } else if (ACPI_FAILURE((status = acpi_wmi_read_wdg_blocks(sc, sc->wmi_handle)))) { device_printf(sc->wmi_dev, "couldn't parse _WDG - %s\n", AcpiFormatException(status)); AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY, acpi_wmi_notify_handler); AcpiRemoveAddressSpaceHandler(sc->wmi_handle, ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler); } else { sc->wmistat_dev_t = make_dev(&wmistat_cdevsw, 0, UID_ROOT, GID_WHEEL, 0644, "wmistat%d", device_get_unit(dev)); sc->wmistat_dev_t->si_drv1 = sc; sc->wmistat_open_pid = 0; sc->wmistat_bufptr = -1; ret = 0; } ACPI_SERIAL_END(acpi_wmi); if (ret == 0) { bus_generic_probe(dev); ret = bus_generic_attach(dev); } return (ret); } /* * Detach the driver by: * - Removing notification handler * - Removing address space handler * - Turning off event generation for all WExx event activated by * child drivers */ static int acpi_wmi_detach(device_t dev) { struct wmi_info *winfo, *tmp; struct acpi_wmi_softc *sc; int ret; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); ACPI_SERIAL_BEGIN(acpi_wmi); if (sc->wmistat_open_pid != 0) { ret = EBUSY; } else { AcpiRemoveNotifyHandler(sc->wmi_handle, ACPI_DEVICE_NOTIFY, acpi_wmi_notify_handler); AcpiRemoveAddressSpaceHandler(sc->wmi_handle, ACPI_ADR_SPACE_EC, acpi_wmi_ec_handler); TAILQ_FOREACH_SAFE(winfo, &sc->wmi_info_list, wmi_list, tmp) { if (winfo->event_handler) acpi_wmi_toggle_we_event_generation(dev, winfo, EVENT_GENERATION_OFF); TAILQ_REMOVE(&sc->wmi_info_list, winfo, wmi_list); free(winfo, M_ACPIWMI); } if (sc->wmistat_bufptr != -1) { sbuf_delete(&sc->wmistat_sbuf); sc->wmistat_bufptr = -1; } sc->wmistat_open_pid = 0; destroy_dev(sc->wmistat_dev_t); ret = 0; } ACPI_SERIAL_END(acpi_wmi); return (ret); } /* * Check if the given GUID string (human readable format * AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP) * exists within _WDG */ static int acpi_wmi_provides_guid_string_method(device_t dev, const char *guid_string) { struct acpi_wmi_softc *sc; struct wmi_info *winfo; int ret; sc = device_get_softc(dev); ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_BEGIN(acpi_wmi); winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string); ret = (winfo == NULL)?0:winfo->ginfo.max_instance+1; ACPI_SERIAL_END(acpi_wmi); return (ret); } /* * Call a method "method_id" on the given GUID block * write result into user provided output buffer */ static ACPI_STATUS acpi_wmi_evaluate_call_method(device_t dev, const char *guid_string, UINT8 instance, UINT32 method_id, const ACPI_BUFFER *in, ACPI_BUFFER *out) { ACPI_OBJECT params[3]; ACPI_OBJECT_LIST input; char method[5] = "WMxx"; struct wmi_info *winfo; struct acpi_wmi_softc *sc; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); ACPI_SERIAL_BEGIN(acpi_wmi); if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string)) == NULL) status = AE_NOT_FOUND; else if (!(winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD)) status = AE_BAD_DATA; else if (instance > winfo->ginfo.max_instance) status = AE_BAD_PARAMETER; else { params[0].Type = ACPI_TYPE_INTEGER; params[0].Integer.Value = instance; params[1].Type = ACPI_TYPE_INTEGER; params[1].Integer.Value = method_id; input.Pointer = params; input.Count = 2; if (in) { params[2].Type = (winfo->ginfo.flags & ACPI_WMI_REGFLAG_STRING) ?ACPI_TYPE_STRING:ACPI_TYPE_BUFFER; params[2].Buffer.Length = in->Length; params[2].Buffer.Pointer = in->Pointer; input.Count = 3; } method[2] = winfo->ginfo.oid[0]; method[3] = winfo->ginfo.oid[1]; status = AcpiEvaluateObject(sc->wmi_handle, method, &input, out); } ACPI_SERIAL_END(acpi_wmi); return (status); } /* * Install a user provided event_handler on the given GUID * provided *data will be passed on callback * If there is already an existing event handler registered it will be silently * discarded */ static ACPI_STATUS acpi_wmi_install_event_handler_method(device_t dev, const char *guid_string, ACPI_NOTIFY_HANDLER event_handler, void *data) { struct acpi_wmi_softc *sc = device_get_softc(dev); struct wmi_info *winfo; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); status = AE_OK; ACPI_SERIAL_BEGIN(acpi_wmi); if (guid_string == NULL || event_handler == NULL) status = AE_BAD_PARAMETER; else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string)) == NULL) status = AE_NOT_EXIST; else if (winfo->event_handler != NULL || (status = acpi_wmi_toggle_we_event_generation(dev, winfo, EVENT_GENERATION_ON)) == AE_OK) { winfo->event_handler = event_handler; winfo->event_handler_user_data = data; } ACPI_SERIAL_END(acpi_wmi); return (status); } /* * Remove a previously installed event handler from the given GUID * If there was none installed, this call is silently discarded and * reported as AE_OK */ static ACPI_STATUS acpi_wmi_remove_event_handler_method(device_t dev, const char *guid_string) { struct acpi_wmi_softc *sc = device_get_softc(dev); struct wmi_info *winfo; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); status = AE_OK; ACPI_SERIAL_BEGIN(acpi_wmi); if (guid_string && (winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string)) != NULL && winfo->event_handler) { status = acpi_wmi_toggle_we_event_generation(dev, winfo, EVENT_GENERATION_OFF); winfo->event_handler = NULL; winfo->event_handler_user_data = NULL; } ACPI_SERIAL_END(acpi_wmi); return (status); } /* * Get details on an event received through a callback registered * through ACPI_WMI_REMOVE_EVENT_HANDLER into a user provided output buffer. * (event_id equals "notify" passed in the callback) */ static ACPI_STATUS acpi_wmi_get_event_data_method(device_t dev, UINT32 event_id, ACPI_BUFFER *out) { ACPI_OBJECT_LIST input; ACPI_OBJECT params[1]; struct acpi_wmi_softc *sc; struct wmi_info *winfo; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); status = AE_NOT_FOUND; ACPI_SERIAL_BEGIN(acpi_wmi); params[0].Type = ACPI_TYPE_INTEGER; params[0].Integer.Value = event_id; input.Pointer = params; input.Count = 1; TAILQ_FOREACH(winfo, &sc->wmi_info_list, wmi_list) { if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) && ((UINT8) winfo->ginfo.oid[0] == event_id)) { status = AcpiEvaluateObject(sc->wmi_handle, "_WED", &input, out); break; } } ACPI_SERIAL_END(acpi_wmi); return (status); } /* * Read a block of data from the given GUID (using WQxx (query)) * Will be returned in a user provided buffer (out). * If the method is marked as expensive (ACPI_WMI_REGFLAG_EXPENSIVE) * we will first call the WCxx control method to lock the node to * lock the node for data collection and release it afterwards. * (Failed WCxx calls are ignored to "support" broken implementations) */ static ACPI_STATUS acpi_wmi_get_block_method(device_t dev, const char *guid_string, UINT8 instance, ACPI_BUFFER *out) { char wc_method[5] = "WCxx"; char wq_method[5] = "WQxx"; ACPI_OBJECT_LIST wc_input; ACPI_OBJECT_LIST wq_input; ACPI_OBJECT wc_params[1]; ACPI_OBJECT wq_params[1]; ACPI_HANDLE wc_handle; struct acpi_wmi_softc *sc; struct wmi_info *winfo; ACPI_STATUS status; ACPI_STATUS wc_status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); wc_status = AE_ERROR; ACPI_SERIAL_BEGIN(acpi_wmi); if (guid_string == NULL || out == NULL) status = AE_BAD_PARAMETER; else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string)) == NULL) status = AE_ERROR; else if (instance > winfo->ginfo.max_instance) status = AE_BAD_PARAMETER; else if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) || (winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD)) status = AE_ERROR; else { wq_params[0].Type = ACPI_TYPE_INTEGER; wq_params[0].Integer.Value = instance; wq_input.Pointer = wq_params; wq_input.Count = 1; if (winfo->ginfo.flags & ACPI_WMI_REGFLAG_EXPENSIVE) { wc_params[0].Type = ACPI_TYPE_INTEGER; wc_params[0].Integer.Value = 1; wc_input.Pointer = wc_params; wc_input.Count = 1; wc_method[2] = winfo->ginfo.oid[0]; wc_method[3] = winfo->ginfo.oid[1]; wc_status = AcpiGetHandle(sc->wmi_handle, wc_method, &wc_handle); if (ACPI_SUCCESS(wc_status)) wc_status = AcpiEvaluateObject(wc_handle, wc_method, &wc_input, NULL); } wq_method[2] = winfo->ginfo.oid[0]; wq_method[3] = winfo->ginfo.oid[1]; status = AcpiEvaluateObject(sc->wmi_handle, wq_method, &wq_input, out); if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EXPENSIVE) && ACPI_SUCCESS(wc_status)) { wc_params[0].Integer.Value = 0; status = AcpiEvaluateObject(wc_handle, wc_method, &wc_input, NULL); /* XXX this might be the wrong status to return? */ } } ACPI_SERIAL_END(acpi_wmi); return (status); } /* * Write a block of data to the given GUID (using WSxx) */ static ACPI_STATUS acpi_wmi_set_block_method(device_t dev, const char *guid_string, UINT8 instance, const ACPI_BUFFER *in) { char method[5] = "WSxx"; ACPI_OBJECT_LIST input; ACPI_OBJECT params[2]; struct wmi_info *winfo; struct acpi_wmi_softc *sc; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); ACPI_SERIAL_BEGIN(acpi_wmi); if (guid_string == NULL || in == NULL) status = AE_BAD_DATA; else if ((winfo = acpi_wmi_lookup_wmi_info_by_guid_string(sc, guid_string)) == NULL) status = AE_ERROR; else if (instance > winfo->ginfo.max_instance) status = AE_BAD_PARAMETER; else if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) || (winfo->ginfo.flags & ACPI_WMI_REGFLAG_METHOD)) status = AE_ERROR; else { params[0].Type = ACPI_TYPE_INTEGER; params[0].Integer.Value = instance; input.Pointer = params; input.Count = 2; params[1].Type = (winfo->ginfo.flags & ACPI_WMI_REGFLAG_STRING) ?ACPI_TYPE_STRING:ACPI_TYPE_BUFFER; params[1].Buffer.Length = in->Length; params[1].Buffer.Pointer = in->Pointer; method[2] = winfo->ginfo.oid[0]; method[3] = winfo->ginfo.oid[1]; status = AcpiEvaluateObject(sc->wmi_handle, method, &input, NULL); } ACPI_SERIAL_END(acpi_wmi); return (status); } /* * Handle events received and dispatch them to * stakeholders that registered through ACPI_WMI_INSTALL_EVENT_HANDLER */ static void acpi_wmi_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_wmi_softc *sc = context; ACPI_NOTIFY_HANDLER handler; void *handler_data; struct wmi_info *winfo; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); handler = NULL; handler_data = NULL; ACPI_SERIAL_BEGIN(acpi_wmi); TAILQ_FOREACH(winfo, &sc->wmi_info_list, wmi_list) { if ((winfo->ginfo.flags & ACPI_WMI_REGFLAG_EVENT) && ((UINT8) winfo->ginfo.oid[0] == notify)) { if (winfo->event_handler) { handler = winfo->event_handler; handler_data = winfo->event_handler_user_data; break; } } } ACPI_SERIAL_END(acpi_wmi); if (handler) { handler(h, notify, handler_data); } } /* * Handle EC address space notifications reveived on the WDG node * (this mimics EcAddressSpaceHandler in acpi_ec.c) */ static ACPI_STATUS acpi_wmi_ec_handler(UINT32 function, ACPI_PHYSICAL_ADDRESS address, UINT32 width, UINT64 *value, void *context, void *region_context) { struct acpi_wmi_softc *sc; int i; UINT64 ec_data; UINT8 ec_addr; ACPI_STATUS status; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, (UINT32)address); sc = (struct acpi_wmi_softc *)context; if (width % 8 != 0 || value == NULL || context == NULL) return (AE_BAD_PARAMETER); if (address + (width / 8) - 1 > 0xFF) return (AE_BAD_ADDRESS); if (function == ACPI_READ) *value = 0; ec_addr = address; status = AE_ERROR; for (i = 0; i < width; i += 8, ++ec_addr) { switch (function) { case ACPI_READ: status = ACPI_EC_READ(sc->ec_dev, ec_addr, &ec_data, 1); if (ACPI_SUCCESS(status)) *value |= ((UINT64)ec_data) << i; break; case ACPI_WRITE: ec_data = (UINT8)((*value) >> i); status = ACPI_EC_WRITE(sc->ec_dev, ec_addr, ec_data, 1); break; default: device_printf(sc->wmi_dev, "invalid acpi_wmi_ec_handler function %d\n", function); status = AE_BAD_PARAMETER; break; } if (ACPI_FAILURE(status)) break; } return (status); } /* * Read GUID blocks from the _WDG node * into wmi_info_list. */ static ACPI_STATUS acpi_wmi_read_wdg_blocks(struct acpi_wmi_softc *sc, ACPI_HANDLE h) { ACPI_BUFFER out = {ACPI_ALLOCATE_BUFFER, NULL}; struct guid_info *ginfo; ACPI_OBJECT *obj; struct wmi_info *winfo; UINT32 i; UINT32 wdg_block_count; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(acpi_wmi); if (ACPI_FAILURE(status = AcpiEvaluateObject(h, "_WDG", NULL, &out))) return (status); obj = (ACPI_OBJECT*) out.Pointer; wdg_block_count = obj->Buffer.Length / sizeof(struct guid_info); if ((ginfo = malloc(obj->Buffer.Length, M_ACPIWMI, M_NOWAIT)) == NULL) { AcpiOsFree(out.Pointer); return (AE_NO_MEMORY); } memcpy(ginfo, obj->Buffer.Pointer, obj->Buffer.Length); for (i = 0; i < wdg_block_count; ++i) { if ((winfo = malloc(sizeof(struct wmi_info), M_ACPIWMI, M_NOWAIT | M_ZERO)) == NULL) { AcpiOsFree(out.Pointer); free(ginfo, M_ACPIWMI); return (AE_NO_MEMORY); } winfo->ginfo = ginfo[i]; TAILQ_INSERT_TAIL(&sc->wmi_info_list, winfo, wmi_list); } AcpiOsFree(out.Pointer); free(ginfo, M_ACPIWMI); return (status); } /* * Toggle event generation in for the given GUID (passed by winfo) * Turn on to get notified (through acpi_wmi_notify_handler) if events happen * on the given GUID. */ static ACPI_STATUS acpi_wmi_toggle_we_event_generation(device_t dev, struct wmi_info *winfo, enum event_generation_state state) { char method[5] = "WExx"; ACPI_OBJECT_LIST input; ACPI_OBJECT params[1]; struct acpi_wmi_softc *sc; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); ACPI_SERIAL_ASSERT(acpi_wmi); params[0].Type = ACPI_TYPE_INTEGER; params[0].Integer.Value = state==EVENT_GENERATION_ON?1:0; input.Pointer = params; input.Count = 1; UINT8 hi = ((UINT8) winfo->ginfo.oid[0]) >> 4; UINT8 lo = ((UINT8) winfo->ginfo.oid[0]) & 0xf; method[2] = (hi > 9 ? hi + 55: hi + 48); method[3] = (lo > 9 ? lo + 55: lo + 48); status = AcpiEvaluateObject(sc->wmi_handle, method, &input, NULL); if (status == AE_NOT_FOUND) status = AE_OK; return (status); } /* * Convert given two digit hex string (hexin) to an UINT8 referenced * by byteout. * Return != 0 if the was a problem (invalid input) */ static __inline int acpi_wmi_hex_to_int(const UINT8 *hexin, UINT8 *byteout) { unsigned int hi; unsigned int lo; hi = hexin[0]; lo = hexin[1]; if ('0' <= hi && hi <= '9') hi -= '0'; else if ('A' <= hi && hi <= 'F') hi -= ('A' - 10); else if ('a' <= hi && hi <= 'f') hi -= ('a' - 10); else return (1); if ('0' <= lo && lo <= '9') lo -= '0'; else if ('A' <= lo && lo <= 'F') lo -= ('A' - 10); else if ('a' <= lo && lo <= 'f') lo -= ('a' - 10); else return (1); *byteout = (hi << 4) + lo; return (0); } /* * Convert a human readable 36 character GUID into a 16byte * machine readable one. * The basic algorithm looks as follows: * Input: AABBCCDD-EEFF-GGHH-IIJJ-KKLLMMNNOOPP * Output: DCBAFEHGIJKLMNOP * (AA BB CC etc. represent two digit hex numbers == bytes) * Return != 0 if passed guid string is invalid */ static int acpi_wmi_guid_string_to_guid(const UINT8 *guid_string, UINT8 *guid) { static const int mapping[20] = {3, 2, 1, 0, -1, 5, 4, -1, 7, 6, -1, 8, 9, -1, 10, 11, 12, 13, 14, 15}; int i; for (i = 0; i < 20; ++i, ++guid_string) { if (mapping[i] >= 0) { if (acpi_wmi_hex_to_int(guid_string, &guid[mapping[i]])) return (-1); ++guid_string; } else if (*guid_string != '-') return (-1); } return (0); } /* * Lookup a wmi_info structure in wmi_list based on a * human readable GUID * Return NULL if the GUID is unknown in the _WDG */ static struct wmi_info* acpi_wmi_lookup_wmi_info_by_guid_string(struct acpi_wmi_softc *sc, const char *guid_string) { char guid[16]; struct wmi_info *winfo; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); ACPI_SERIAL_ASSERT(acpi_wmi); if (!acpi_wmi_guid_string_to_guid(guid_string, guid)) { TAILQ_FOREACH(winfo, &sc->wmi_info_list, wmi_list) { if (!memcmp(winfo->ginfo.guid, guid, 16)) { return (winfo); } } } return (NULL); } /* * open wmistat device */ static int acpi_wmi_wmistat_open(struct cdev* dev, int flags, int mode, struct thread *td) { struct acpi_wmi_softc *sc; int ret; if (dev == NULL || dev->si_drv1 == NULL) return (EBADF); sc = dev->si_drv1; ACPI_SERIAL_BEGIN(acpi_wmi); if (sc->wmistat_open_pid != 0) { ret = EBUSY; } else { if (sbuf_new(&sc->wmistat_sbuf, NULL, 4096, SBUF_AUTOEXTEND) == NULL) { ret = ENXIO; } else { sc->wmistat_open_pid = td->td_proc->p_pid; sc->wmistat_bufptr = 0; ret = 0; } } ACPI_SERIAL_END(acpi_wmi); return (ret); } /* * close wmistat device */ static int acpi_wmi_wmistat_close(struct cdev* dev, int flags, int mode, struct thread *td) { struct acpi_wmi_softc *sc; int ret; if (dev == NULL || dev->si_drv1 == NULL) return (EBADF); sc = dev->si_drv1; ACPI_SERIAL_BEGIN(acpi_wmi); if (sc->wmistat_open_pid == 0) { ret = EBADF; } else { if (sc->wmistat_bufptr != -1) { sbuf_delete(&sc->wmistat_sbuf); sc->wmistat_bufptr = -1; } sc->wmistat_open_pid = 0; ret = 0; } ACPI_SERIAL_END(acpi_wmi); return (ret); } /* * Read from wmistat guid information */ static int acpi_wmi_wmistat_read(struct cdev *dev, struct uio *buf, int flag) { struct acpi_wmi_softc *sc; struct wmi_info *winfo; int l; int ret; UINT8* guid; if (dev == NULL || dev->si_drv1 == NULL) return (EBADF); sc = dev->si_drv1; ACPI_SERIAL_BEGIN(acpi_wmi); if (sc->wmistat_open_pid != buf->uio_td->td_proc->p_pid || sc->wmistat_bufptr == -1) { ret = EBADF; } else { if (!sbuf_done(&sc->wmistat_sbuf)) { sbuf_printf(&sc->wmistat_sbuf, "GUID " " INST EXPE METH STR " "EVENT OID\n"); TAILQ_FOREACH(winfo, &sc->wmi_info_list, wmi_list) { guid = (UINT8*)winfo->ginfo.guid; sbuf_printf(&sc->wmistat_sbuf, "{%02X%02X%02X%02X-%02X%02X-" "%02X%02X-%02X%02X-%02X%02X" "%02X%02X%02X%02X} %3d %-5s", guid[3], guid[2], guid[1], guid[0], guid[5], guid[4], guid[7], guid[6], guid[8], guid[9], guid[10], guid[11], guid[12], guid[13], guid[14], guid[15], winfo->ginfo.max_instance, (winfo->ginfo.flags& ACPI_WMI_REGFLAG_EXPENSIVE)? "YES":"NO" ); if (winfo->ginfo.flags&ACPI_WMI_REGFLAG_METHOD) sbuf_printf(&sc->wmistat_sbuf, "WM%c%c ", winfo->ginfo.oid[0], winfo->ginfo.oid[1]); else sbuf_printf(&sc->wmistat_sbuf, "NO "); sbuf_printf(&sc->wmistat_sbuf, "%-4s", (winfo->ginfo.flags& ACPI_WMI_REGFLAG_STRING)?"YES":"NO" ); if (winfo->ginfo.flags&ACPI_WMI_REGFLAG_EVENT) sbuf_printf(&sc->wmistat_sbuf, "0x%02X%s -\n", (UINT8)winfo->ginfo.oid[0], winfo->event_handler==NULL? " ":"+"); else sbuf_printf(&sc->wmistat_sbuf, "NO %c%c\n", winfo->ginfo.oid[0], winfo->ginfo.oid[1]); } sbuf_finish(&sc->wmistat_sbuf); } if (sbuf_len(&sc->wmistat_sbuf) <= 0) { sbuf_delete(&sc->wmistat_sbuf); sc->wmistat_bufptr = -1; sc->wmistat_open_pid = 0; ret = ENOMEM; } else { l = min(buf->uio_resid, sbuf_len(&sc->wmistat_sbuf) - sc->wmistat_bufptr); ret = (l > 0)?uiomove(sbuf_data(&sc->wmistat_sbuf) + sc->wmistat_bufptr, l, buf) : 0; sc->wmistat_bufptr += l; } } ACPI_SERIAL_END(acpi_wmi); return (ret); } Index: head/sys/dev/acpi_support/atk0110.c =================================================================== --- head/sys/dev/acpi_support/atk0110.c (revision 339753) +++ head/sys/dev/acpi_support/atk0110.c (revision 339754) @@ -1,581 +1,583 @@ /* $NetBSD: atk0110.c,v 1.4 2010/02/11 06:54:57 cnst Exp $ */ /* $OpenBSD: atk0110.c,v 1.1 2009/07/23 01:38:16 cnst Exp $ */ /* * Copyright (c) 2009, 2010 Constantine A. Murenin * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include /* * ASUSTeK AI Booster (ACPI ASOC ATK0110). * * This code was originally written for OpenBSD after the techniques * described in the Linux's asus_atk0110.c and FreeBSD's Takanori Watanabe's * acpi_aiboost.c were verified to be accurate on the actual hardware kindly * provided by Sam Fourman Jr. It was subsequently ported from OpenBSD to * DragonFly BSD, to NetBSD's sysmon_envsys(9) and to FreeBSD's sysctl(9). * * -- Constantine A. Murenin */ #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("aibs"); ACPI_SERIAL_DECL(aibs, "aibs"); #define AIBS_MORE_SENSORS #define AIBS_VERBOSE #define AIBS_GROUP_SENSORS 0x06 #define AIBS_SENS_TYPE(x) (((x) >> 16) & 0xff) #define AIBS_SENS_TYPE_VOLT 2 #define AIBS_SENS_TYPE_TEMP 3 #define AIBS_SENS_TYPE_FAN 4 #define AIBS_SENS_TYPE_VOLT_NAME "volt" #define AIBS_SENS_TYPE_VOLT_TEMP "temp" #define AIBS_SENS_TYPE_VOLT_FAN "fan" struct aibs_sensor { ACPI_INTEGER v; ACPI_INTEGER i; ACPI_INTEGER l; ACPI_INTEGER h; int t; }; struct aibs_softc { device_t sc_dev; ACPI_HANDLE sc_ah; struct aibs_sensor *sc_asens_volt; struct aibs_sensor *sc_asens_temp; struct aibs_sensor *sc_asens_fan; struct aibs_sensor *sc_asens_all; struct sysctl_oid *sc_volt_sysctl; struct sysctl_oid *sc_temp_sysctl; struct sysctl_oid *sc_fan_sysctl; bool sc_ggrp_method; }; static int aibs_probe(device_t); static int aibs_attach(device_t); static int aibs_detach(device_t); static int aibs_sysctl(SYSCTL_HANDLER_ARGS); static int aibs_sysctl_ggrp(SYSCTL_HANDLER_ARGS); static int aibs_attach_ggrp(struct aibs_softc *); static int aibs_attach_sif(struct aibs_softc *, int); static device_method_t aibs_methods[] = { DEVMETHOD(device_probe, aibs_probe), DEVMETHOD(device_attach, aibs_attach), DEVMETHOD(device_detach, aibs_detach), { NULL, NULL } }; static driver_t aibs_driver = { "aibs", aibs_methods, sizeof(struct aibs_softc) }; static devclass_t aibs_devclass; DRIVER_MODULE(aibs, acpi, aibs_driver, aibs_devclass, NULL, NULL); MODULE_DEPEND(aibs, acpi, 1, 1, 1); static char* aibs_hids[] = { "ATK0110", NULL }; static int aibs_probe(device_t dev) { - if (acpi_disabled("aibs") || - ACPI_ID_PROBE(device_get_parent(dev), dev, aibs_hids) == NULL) - return (ENXIO); + int rv; - device_set_desc(dev, "ASUSTeK AI Booster (ACPI ASOC ATK0110)"); + if (acpi_disabled("aibs")) + return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, aibs_hids, NULL); + if (rv <= 0 ) + device_set_desc(dev, "ASUSTeK AI Booster (ACPI ASOC ATK0110)"); return (0); } static int aibs_attach(device_t dev) { struct aibs_softc *sc = device_get_softc(dev); int err; sc->sc_dev = dev; sc->sc_ah = acpi_get_handle(dev); sc->sc_ggrp_method = false; err = aibs_attach_sif(sc, AIBS_SENS_TYPE_VOLT); if (err == 0) err = aibs_attach_sif(sc, AIBS_SENS_TYPE_TEMP); if (err == 0) err = aibs_attach_sif(sc, AIBS_SENS_TYPE_FAN); if (err == 0) return (0); /* Clean up whatever was allocated earlier. */ if (sc->sc_volt_sysctl != NULL) sysctl_remove_oid(sc->sc_volt_sysctl, true, true); if (sc->sc_temp_sysctl != NULL) sysctl_remove_oid(sc->sc_temp_sysctl, true, true); if (sc->sc_fan_sysctl != NULL) sysctl_remove_oid(sc->sc_fan_sysctl, true, true); aibs_detach(dev); sc->sc_ggrp_method = true; err = aibs_attach_ggrp(sc); return (err); } static int aibs_add_sensor(struct aibs_softc *sc, ACPI_OBJECT *o, struct aibs_sensor* sensor, const char ** descr) { int off; /* * Packages for the old and new methods are quite * similar except that the new package has two * new (unknown / unused) fields after the name field. */ if (sc->sc_ggrp_method) off = 4; else off = 2; if (o->Type != ACPI_TYPE_PACKAGE) { device_printf(sc->sc_dev, "sensor object is not a package: %i type\n", o->Type); return (ENXIO); } if (o[0].Package.Count != (off + 3) || o->Package.Elements[0].Type != ACPI_TYPE_INTEGER || o->Package.Elements[1].Type != ACPI_TYPE_STRING || o->Package.Elements[off].Type != ACPI_TYPE_INTEGER || o->Package.Elements[off + 1].Type != ACPI_TYPE_INTEGER || o->Package.Elements[off + 2].Type != ACPI_TYPE_INTEGER) { device_printf(sc->sc_dev, "unexpected package content\n"); return (ENXIO); } sensor->i = o->Package.Elements[0].Integer.Value; *descr = o->Package.Elements[1].String.Pointer; sensor->l = o->Package.Elements[off].Integer.Value; sensor->h = o->Package.Elements[off + 1].Integer.Value; /* For the new method the second value is a range size. */ if (sc->sc_ggrp_method) sensor->h += sensor->l; sensor->t = AIBS_SENS_TYPE(sensor->i); switch (sensor->t) { case AIBS_SENS_TYPE_VOLT: case AIBS_SENS_TYPE_TEMP: case AIBS_SENS_TYPE_FAN: return (0); default: device_printf(sc->sc_dev, "unknown sensor type 0x%x", sensor->t); return (ENXIO); } } static void aibs_sensor_added(struct aibs_softc *sc, struct sysctl_oid *so, const char *type_name, int idx, struct aibs_sensor *sensor, const char *descr) { char sysctl_name[8]; snprintf(sysctl_name, sizeof(sysctl_name), "%i", idx); #ifdef AIBS_VERBOSE device_printf(sc->sc_dev, "%c%i: 0x%08jx %20s %5jd / %5jd\n", type_name[0], idx, (uintmax_t)sensor->i, descr, (intmax_t)sensor->l, (intmax_t)sensor->h); #endif SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->sc_dev), SYSCTL_CHILDREN(so), idx, sysctl_name, CTLTYPE_INT | CTLFLAG_RD, sc, (uintptr_t)sensor, sc->sc_ggrp_method ? aibs_sysctl_ggrp : aibs_sysctl, sensor->t == AIBS_SENS_TYPE_TEMP ? "IK" : "I", descr); } static int aibs_attach_ggrp(struct aibs_softc *sc) { ACPI_STATUS s; ACPI_BUFFER buf; ACPI_HANDLE h; ACPI_OBJECT id; ACPI_OBJECT *bp; ACPI_OBJECT_LIST arg; int i; int t, v, f; int err; int *s_idx; const char *name; const char *descr; struct aibs_sensor *sensor; struct sysctl_oid **so; /* First see if GITM is available. */ s = AcpiGetHandle(sc->sc_ah, "GITM", &h); if (ACPI_FAILURE(s)) { if (bootverbose) device_printf(sc->sc_dev, "GITM not found\n"); return (ENXIO); } /* * Now call GGRP with the appropriate argument to list sensors. * The method lists different groups of entities depending on * the argument. */ id.Integer.Value = AIBS_GROUP_SENSORS; id.Type = ACPI_TYPE_INTEGER; arg.Count = 1; arg.Pointer = &id; buf.Length = ACPI_ALLOCATE_BUFFER; buf.Pointer = NULL; s = AcpiEvaluateObjectTyped(sc->sc_ah, "GGRP", &arg, &buf, ACPI_TYPE_PACKAGE); if (ACPI_FAILURE(s)) { device_printf(sc->sc_dev, "GGRP not found\n"); return (ENXIO); } bp = buf.Pointer; sc->sc_asens_all = malloc(sizeof(*sc->sc_asens_all) * bp->Package.Count, M_DEVBUF, M_WAITOK | M_ZERO); v = t = f = 0; for (i = 0; i < bp->Package.Count; i++) { sensor = &sc->sc_asens_all[i]; err = aibs_add_sensor(sc, &bp->Package.Elements[i], sensor, &descr); if (err != 0) continue; switch (sensor->t) { case AIBS_SENS_TYPE_VOLT: name = "volt"; so = &sc->sc_volt_sysctl; s_idx = &v; break; case AIBS_SENS_TYPE_TEMP: name = "temp"; so = &sc->sc_temp_sysctl; s_idx = &t; break; case AIBS_SENS_TYPE_FAN: name = "fan"; so = &sc->sc_fan_sysctl; s_idx = &f; break; default: panic("add_sensor succeeded for unknown sensor type %d", sensor->t); } if (*so == NULL) { /* sysctl subtree for sensors of this type */ *so = SYSCTL_ADD_NODE(device_get_sysctl_ctx(sc->sc_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)), sensor->t, name, CTLFLAG_RD, NULL, NULL); } aibs_sensor_added(sc, *so, name, *s_idx, sensor, descr); *s_idx += 1; } AcpiOsFree(buf.Pointer); return (0); } static int aibs_attach_sif(struct aibs_softc *sc, int st) { char name[] = "?SIF"; ACPI_STATUS s; ACPI_BUFFER b; ACPI_OBJECT *bp, *o; const char *node; struct aibs_sensor *as; struct sysctl_oid **so; int i, n; int err; switch (st) { case AIBS_SENS_TYPE_VOLT: node = "volt"; name[0] = 'V'; so = &sc->sc_volt_sysctl; break; case AIBS_SENS_TYPE_TEMP: node = "temp"; name[0] = 'T'; so = &sc->sc_temp_sysctl; break; case AIBS_SENS_TYPE_FAN: node = "fan"; name[0] = 'F'; so = &sc->sc_fan_sysctl; break; default: panic("Unsupported sensor type %d", st); } b.Length = ACPI_ALLOCATE_BUFFER; s = AcpiEvaluateObjectTyped(sc->sc_ah, name, NULL, &b, ACPI_TYPE_PACKAGE); if (ACPI_FAILURE(s)) { device_printf(sc->sc_dev, "%s not found\n", name); return (ENXIO); } bp = b.Pointer; o = bp->Package.Elements; if (o[0].Type != ACPI_TYPE_INTEGER) { device_printf(sc->sc_dev, "%s[0]: invalid type\n", name); AcpiOsFree(b.Pointer); return (ENXIO); } n = o[0].Integer.Value; if (bp->Package.Count - 1 < n) { device_printf(sc->sc_dev, "%s: invalid package\n", name); AcpiOsFree(b.Pointer); return (ENXIO); } else if (bp->Package.Count - 1 > n) { int on = n; #ifdef AIBS_MORE_SENSORS n = bp->Package.Count - 1; #endif device_printf(sc->sc_dev, "%s: malformed package: %i/%i" ", assume %i\n", name, on, bp->Package.Count - 1, n); } if (n < 1) { device_printf(sc->sc_dev, "%s: no members in the package\n", name); AcpiOsFree(b.Pointer); return (ENXIO); } as = malloc(sizeof(*as) * n, M_DEVBUF, M_WAITOK | M_ZERO); switch (st) { case AIBS_SENS_TYPE_VOLT: sc->sc_asens_volt = as; break; case AIBS_SENS_TYPE_TEMP: sc->sc_asens_temp = as; break; case AIBS_SENS_TYPE_FAN: sc->sc_asens_fan = as; break; } /* sysctl subtree for sensors of this type */ *so = SYSCTL_ADD_NODE(device_get_sysctl_ctx(sc->sc_dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->sc_dev)), st, node, CTLFLAG_RD, NULL, NULL); for (i = 0, o++; i < n; i++, o++) { const char *descr; err = aibs_add_sensor(sc, o, &as[i], &descr); if (err == 0) aibs_sensor_added(sc, *so, node, i, &as[i], descr); } AcpiOsFree(b.Pointer); return (0); } static int aibs_detach(device_t dev) { struct aibs_softc *sc = device_get_softc(dev); if (sc->sc_asens_volt != NULL) free(sc->sc_asens_volt, M_DEVBUF); if (sc->sc_asens_temp != NULL) free(sc->sc_asens_temp, M_DEVBUF); if (sc->sc_asens_fan != NULL) free(sc->sc_asens_fan, M_DEVBUF); if (sc->sc_asens_all != NULL) free(sc->sc_asens_all, M_DEVBUF); return (0); } #ifdef AIBS_VERBOSE #define ddevice_printf(x...) device_printf(x) #else #define ddevice_printf(x...) #endif static int aibs_sysctl(SYSCTL_HANDLER_ARGS) { struct aibs_softc *sc = arg1; struct aibs_sensor *sensor = (void *)(intptr_t)arg2; int i = oidp->oid_number; ACPI_STATUS rs; ACPI_OBJECT p, *bp; ACPI_OBJECT_LIST mp; ACPI_BUFFER b; char *name; ACPI_INTEGER v, l, h; int so[3]; switch (sensor->t) { case AIBS_SENS_TYPE_VOLT: name = "RVLT"; break; case AIBS_SENS_TYPE_TEMP: name = "RTMP"; break; case AIBS_SENS_TYPE_FAN: name = "RFAN"; break; default: return (ENOENT); } l = sensor->l; h = sensor->h; p.Type = ACPI_TYPE_INTEGER; p.Integer.Value = sensor->i; mp.Count = 1; mp.Pointer = &p; b.Length = ACPI_ALLOCATE_BUFFER; ACPI_SERIAL_BEGIN(aibs); rs = AcpiEvaluateObjectTyped(sc->sc_ah, name, &mp, &b, ACPI_TYPE_INTEGER); if (ACPI_FAILURE(rs)) { ddevice_printf(sc->sc_dev, "%s: %i: evaluation failed\n", name, i); ACPI_SERIAL_END(aibs); return (EIO); } bp = b.Pointer; v = bp->Integer.Value; AcpiOsFree(b.Pointer); ACPI_SERIAL_END(aibs); switch (sensor->t) { case AIBS_SENS_TYPE_VOLT: break; case AIBS_SENS_TYPE_TEMP: v += 2731; l += 2731; h += 2731; break; case AIBS_SENS_TYPE_FAN: break; } so[0] = v; so[1] = l; so[2] = h; return (sysctl_handle_opaque(oidp, &so, sizeof(so), req)); } static int aibs_sysctl_ggrp(SYSCTL_HANDLER_ARGS) { struct aibs_softc *sc = arg1; struct aibs_sensor *sensor = (void *)(intptr_t)arg2; ACPI_STATUS rs; ACPI_OBJECT p, *bp; ACPI_OBJECT_LIST arg; ACPI_BUFFER buf; ACPI_INTEGER v, l, h; int so[3]; uint32_t *ret; uint32_t cmd[3]; cmd[0] = sensor->i; cmd[1] = 0; cmd[2] = 0; p.Type = ACPI_TYPE_BUFFER; p.Buffer.Pointer = (void *)cmd; p.Buffer.Length = sizeof(cmd); arg.Count = 1; arg.Pointer = &p; buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; ACPI_SERIAL_BEGIN(aibs); rs = AcpiEvaluateObjectTyped(sc->sc_ah, "GITM", &arg, &buf, ACPI_TYPE_BUFFER); ACPI_SERIAL_END(aibs); if (ACPI_FAILURE(rs)) { device_printf(sc->sc_dev, "GITM evaluation failed\n"); return (EIO); } bp = buf.Pointer; if (bp->Buffer.Length < 8) { device_printf(sc->sc_dev, "GITM returned short buffer\n"); return (EIO); } ret = (uint32_t *)bp->Buffer.Pointer; if (ret[0] == 0) { device_printf(sc->sc_dev, "GITM returned error status\n"); return (EINVAL); } v = ret[1]; AcpiOsFree(buf.Pointer); l = sensor->l; h = sensor->h; switch (sensor->t) { case AIBS_SENS_TYPE_VOLT: break; case AIBS_SENS_TYPE_TEMP: v += 2731; l += 2731; h += 2731; break; case AIBS_SENS_TYPE_FAN: break; } so[0] = v; so[1] = l; so[2] = h; return (sysctl_handle_opaque(oidp, &so, sizeof(so), req)); } Index: head/sys/dev/acpica/acpi.c =================================================================== --- head/sys/dev/acpica/acpi.c (revision 339753) +++ head/sys/dev/acpica/acpi.c (revision 339754) @@ -1,4293 +1,4305 @@ /*- * Copyright (c) 2000 Takanori Watanabe * Copyright (c) 2000 Mitsuru IWASAKI * Copyright (c) 2000, 2001 Michael Smith * Copyright (c) 2000 BSDi * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__i386__) || defined(__amd64__) #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_ACPIDEV, "acpidev", "ACPI devices"); /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("ACPI") static d_open_t acpiopen; static d_close_t acpiclose; static d_ioctl_t acpiioctl; static struct cdevsw acpi_cdevsw = { .d_version = D_VERSION, .d_open = acpiopen, .d_close = acpiclose, .d_ioctl = acpiioctl, .d_name = "acpi", }; struct acpi_interface { ACPI_STRING *data; int num; }; static char *sysres_ids[] = { "PNP0C01", "PNP0C02", NULL }; static char *pcilink_ids[] = { "PNP0C0F", NULL }; /* Global mutex for locking access to the ACPI subsystem. */ struct mtx acpi_mutex; struct callout acpi_sleep_timer; /* Bitmap of device quirks. */ int acpi_quirks; /* Supported sleep states. */ static BOOLEAN acpi_sleep_states[ACPI_S_STATE_COUNT]; static void acpi_lookup(void *arg, const char *name, device_t *dev); static int acpi_modevent(struct module *mod, int event, void *junk); static int acpi_probe(device_t dev); static int acpi_attach(device_t dev); static int acpi_suspend(device_t dev); static int acpi_resume(device_t dev); static int acpi_shutdown(device_t dev); static device_t acpi_add_child(device_t bus, u_int order, const char *name, int unit); static int acpi_print_child(device_t bus, device_t child); static void acpi_probe_nomatch(device_t bus, device_t child); static void acpi_driver_added(device_t dev, driver_t *driver); static int acpi_read_ivar(device_t dev, device_t child, int index, uintptr_t *result); static int acpi_write_ivar(device_t dev, device_t child, int index, uintptr_t value); static struct resource_list *acpi_get_rlist(device_t dev, device_t child); static void acpi_reserve_resources(device_t dev); static int acpi_sysres_alloc(device_t dev); static int acpi_set_resource(device_t dev, device_t child, int type, int rid, rman_res_t start, rman_res_t count); static struct resource *acpi_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); static int acpi_adjust_resource(device_t bus, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end); static int acpi_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r); static void acpi_delete_resource(device_t bus, device_t child, int type, int rid); static uint32_t acpi_isa_get_logicalid(device_t dev); static int acpi_isa_get_compatid(device_t dev, uint32_t *cids, int count); -static char *acpi_device_id_probe(device_t bus, device_t dev, char **ids); +static int acpi_device_id_probe(device_t bus, device_t dev, char **ids, char **match); static ACPI_STATUS acpi_device_eval_obj(device_t bus, device_t dev, ACPI_STRING pathname, ACPI_OBJECT_LIST *parameters, ACPI_BUFFER *ret); static ACPI_STATUS acpi_device_scan_cb(ACPI_HANDLE h, UINT32 level, void *context, void **retval); static ACPI_STATUS acpi_device_scan_children(device_t bus, device_t dev, int max_depth, acpi_scan_cb_t user_fn, void *arg); static int acpi_set_powerstate(device_t child, int state); static int acpi_isa_pnp_probe(device_t bus, device_t child, struct isa_pnp_id *ids); static void acpi_probe_children(device_t bus); static void acpi_probe_order(ACPI_HANDLE handle, int *order); static ACPI_STATUS acpi_probe_child(ACPI_HANDLE handle, UINT32 level, void *context, void **status); static void acpi_sleep_enable(void *arg); static ACPI_STATUS acpi_sleep_disable(struct acpi_softc *sc); static ACPI_STATUS acpi_EnterSleepState(struct acpi_softc *sc, int state); static void acpi_shutdown_final(void *arg, int howto); static void acpi_enable_fixed_events(struct acpi_softc *sc); static BOOLEAN acpi_has_hid(ACPI_HANDLE handle); static void acpi_resync_clock(struct acpi_softc *sc); static int acpi_wake_sleep_prep(ACPI_HANDLE handle, int sstate); static int acpi_wake_run_prep(ACPI_HANDLE handle, int sstate); static int acpi_wake_prep_walk(int sstate); static int acpi_wake_sysctl_walk(device_t dev); static int acpi_wake_set_sysctl(SYSCTL_HANDLER_ARGS); static void acpi_system_eventhandler_sleep(void *arg, int state); static void acpi_system_eventhandler_wakeup(void *arg, int state); static int acpi_sname2sstate(const char *sname); static const char *acpi_sstate2sname(int sstate); static int acpi_supported_sleep_state_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_sleep_state_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_debug_objects_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_pm_func(u_long cmd, void *arg, ...); static int acpi_child_location_str_method(device_t acdev, device_t child, char *buf, size_t buflen); static int acpi_child_pnpinfo_str_method(device_t acdev, device_t child, char *buf, size_t buflen); #if defined(__i386__) || defined(__amd64__) static void acpi_enable_pcie(void); #endif static void acpi_hint_device_unit(device_t acdev, device_t child, const char *name, int *unitp); static void acpi_reset_interfaces(device_t dev); static device_method_t acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_probe), DEVMETHOD(device_attach, acpi_attach), DEVMETHOD(device_shutdown, acpi_shutdown), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_suspend, acpi_suspend), DEVMETHOD(device_resume, acpi_resume), /* Bus interface */ DEVMETHOD(bus_add_child, acpi_add_child), DEVMETHOD(bus_print_child, acpi_print_child), DEVMETHOD(bus_probe_nomatch, acpi_probe_nomatch), DEVMETHOD(bus_driver_added, acpi_driver_added), DEVMETHOD(bus_read_ivar, acpi_read_ivar), DEVMETHOD(bus_write_ivar, acpi_write_ivar), DEVMETHOD(bus_get_resource_list, acpi_get_rlist), DEVMETHOD(bus_set_resource, acpi_set_resource), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_alloc_resource, acpi_alloc_resource), DEVMETHOD(bus_adjust_resource, acpi_adjust_resource), DEVMETHOD(bus_release_resource, acpi_release_resource), DEVMETHOD(bus_delete_resource, acpi_delete_resource), DEVMETHOD(bus_child_pnpinfo_str, acpi_child_pnpinfo_str_method), DEVMETHOD(bus_child_location_str, acpi_child_location_str_method), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD(bus_hint_device_unit, acpi_hint_device_unit), DEVMETHOD(bus_get_cpus, acpi_get_cpus), DEVMETHOD(bus_get_domain, acpi_get_domain), /* ACPI bus */ DEVMETHOD(acpi_id_probe, acpi_device_id_probe), DEVMETHOD(acpi_evaluate_object, acpi_device_eval_obj), DEVMETHOD(acpi_pwr_for_sleep, acpi_device_pwr_for_sleep), DEVMETHOD(acpi_scan_children, acpi_device_scan_children), /* ISA emulation */ DEVMETHOD(isa_pnp_probe, acpi_isa_pnp_probe), DEVMETHOD_END }; static driver_t acpi_driver = { "acpi", acpi_methods, sizeof(struct acpi_softc), }; static devclass_t acpi_devclass; DRIVER_MODULE(acpi, nexus, acpi_driver, acpi_devclass, acpi_modevent, 0); MODULE_VERSION(acpi, 1); ACPI_SERIAL_DECL(acpi, "ACPI root bus"); /* Local pools for managing system resources for ACPI child devices. */ static struct rman acpi_rman_io, acpi_rman_mem; #define ACPI_MINIMUM_AWAKETIME 5 /* Holds the description of the acpi0 device. */ static char acpi_desc[ACPI_OEM_ID_SIZE + ACPI_OEM_TABLE_ID_SIZE + 2]; SYSCTL_NODE(_debug, OID_AUTO, acpi, CTLFLAG_RD, NULL, "ACPI debugging"); static char acpi_ca_version[12]; SYSCTL_STRING(_debug_acpi, OID_AUTO, acpi_ca_version, CTLFLAG_RD, acpi_ca_version, 0, "Version of Intel ACPI-CA"); /* * Allow overriding _OSI methods. */ static char acpi_install_interface[256]; TUNABLE_STR("hw.acpi.install_interface", acpi_install_interface, sizeof(acpi_install_interface)); static char acpi_remove_interface[256]; TUNABLE_STR("hw.acpi.remove_interface", acpi_remove_interface, sizeof(acpi_remove_interface)); /* Allow users to dump Debug objects without ACPI debugger. */ static int acpi_debug_objects; TUNABLE_INT("debug.acpi.enable_debug_objects", &acpi_debug_objects); SYSCTL_PROC(_debug_acpi, OID_AUTO, enable_debug_objects, CTLFLAG_RW | CTLTYPE_INT, NULL, 0, acpi_debug_objects_sysctl, "I", "Enable Debug objects"); /* Allow the interpreter to ignore common mistakes in BIOS. */ static int acpi_interpreter_slack = 1; TUNABLE_INT("debug.acpi.interpreter_slack", &acpi_interpreter_slack); SYSCTL_INT(_debug_acpi, OID_AUTO, interpreter_slack, CTLFLAG_RDTUN, &acpi_interpreter_slack, 1, "Turn on interpreter slack mode."); /* Ignore register widths set by FADT and use default widths instead. */ static int acpi_ignore_reg_width = 1; TUNABLE_INT("debug.acpi.default_register_width", &acpi_ignore_reg_width); SYSCTL_INT(_debug_acpi, OID_AUTO, default_register_width, CTLFLAG_RDTUN, &acpi_ignore_reg_width, 1, "Ignore register widths set by FADT"); /* Allow users to override quirks. */ TUNABLE_INT("debug.acpi.quirks", &acpi_quirks); int acpi_susp_bounce; SYSCTL_INT(_debug_acpi, OID_AUTO, suspend_bounce, CTLFLAG_RW, &acpi_susp_bounce, 0, "Don't actually suspend, just test devices."); /* * ACPI can only be loaded as a module by the loader; activating it after * system bootstrap time is not useful, and can be fatal to the system. * It also cannot be unloaded, since the entire system bus hierarchy hangs * off it. */ static int acpi_modevent(struct module *mod, int event, void *junk) { switch (event) { case MOD_LOAD: if (!cold) { printf("The ACPI driver cannot be loaded after boot.\n"); return (EPERM); } break; case MOD_UNLOAD: if (!cold && power_pm_get_type() == POWER_PM_TYPE_ACPI) return (EBUSY); break; default: break; } return (0); } /* * Perform early initialization. */ ACPI_STATUS acpi_Startup(void) { static int started = 0; ACPI_STATUS status; int val; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* Only run the startup code once. The MADT driver also calls this. */ if (started) return_VALUE (AE_OK); started = 1; /* * Initialize the ACPICA subsystem. */ if (ACPI_FAILURE(status = AcpiInitializeSubsystem())) { printf("ACPI: Could not initialize Subsystem: %s\n", AcpiFormatException(status)); return_VALUE (status); } /* * Pre-allocate space for RSDT/XSDT and DSDT tables and allow resizing * if more tables exist. */ if (ACPI_FAILURE(status = AcpiInitializeTables(NULL, 2, TRUE))) { printf("ACPI: Table initialisation failed: %s\n", AcpiFormatException(status)); return_VALUE (status); } /* Set up any quirks we have for this system. */ if (acpi_quirks == ACPI_Q_OK) acpi_table_quirks(&acpi_quirks); /* If the user manually set the disabled hint to 0, force-enable ACPI. */ if (resource_int_value("acpi", 0, "disabled", &val) == 0 && val == 0) acpi_quirks &= ~ACPI_Q_BROKEN; if (acpi_quirks & ACPI_Q_BROKEN) { printf("ACPI disabled by blacklist. Contact your BIOS vendor.\n"); status = AE_SUPPORT; } return_VALUE (status); } /* * Detect ACPI and perform early initialisation. */ int acpi_identify(void) { ACPI_TABLE_RSDP *rsdp; ACPI_TABLE_HEADER *rsdt; ACPI_PHYSICAL_ADDRESS paddr; struct sbuf sb; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (!cold) return (ENXIO); /* Check that we haven't been disabled with a hint. */ if (resource_disabled("acpi", 0)) return (ENXIO); /* Check for other PM systems. */ if (power_pm_get_type() != POWER_PM_TYPE_NONE && power_pm_get_type() != POWER_PM_TYPE_ACPI) { printf("ACPI identify failed, other PM system enabled.\n"); return (ENXIO); } /* Initialize root tables. */ if (ACPI_FAILURE(acpi_Startup())) { printf("ACPI: Try disabling either ACPI or apic support.\n"); return (ENXIO); } if ((paddr = AcpiOsGetRootPointer()) == 0 || (rsdp = AcpiOsMapMemory(paddr, sizeof(ACPI_TABLE_RSDP))) == NULL) return (ENXIO); if (rsdp->Revision > 1 && rsdp->XsdtPhysicalAddress != 0) paddr = (ACPI_PHYSICAL_ADDRESS)rsdp->XsdtPhysicalAddress; else paddr = (ACPI_PHYSICAL_ADDRESS)rsdp->RsdtPhysicalAddress; AcpiOsUnmapMemory(rsdp, sizeof(ACPI_TABLE_RSDP)); if ((rsdt = AcpiOsMapMemory(paddr, sizeof(ACPI_TABLE_HEADER))) == NULL) return (ENXIO); sbuf_new(&sb, acpi_desc, sizeof(acpi_desc), SBUF_FIXEDLEN); sbuf_bcat(&sb, rsdt->OemId, ACPI_OEM_ID_SIZE); sbuf_trim(&sb); sbuf_putc(&sb, ' '); sbuf_bcat(&sb, rsdt->OemTableId, ACPI_OEM_TABLE_ID_SIZE); sbuf_trim(&sb); sbuf_finish(&sb); sbuf_delete(&sb); AcpiOsUnmapMemory(rsdt, sizeof(ACPI_TABLE_HEADER)); snprintf(acpi_ca_version, sizeof(acpi_ca_version), "%x", ACPI_CA_VERSION); return (0); } /* * Fetch some descriptive data from ACPI to put in our attach message. */ static int acpi_probe(device_t dev) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); device_set_desc(dev, acpi_desc); return_VALUE (BUS_PROBE_NOWILDCARD); } static int acpi_attach(device_t dev) { struct acpi_softc *sc; ACPI_STATUS status; int error, state; UINT32 flags; UINT8 TypeA, TypeB; char *env; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->acpi_dev = dev; callout_init(&sc->susp_force_to, 1); error = ENXIO; /* Initialize resource manager. */ acpi_rman_io.rm_type = RMAN_ARRAY; acpi_rman_io.rm_start = 0; acpi_rman_io.rm_end = 0xffff; acpi_rman_io.rm_descr = "ACPI I/O ports"; if (rman_init(&acpi_rman_io) != 0) panic("acpi rman_init IO ports failed"); acpi_rman_mem.rm_type = RMAN_ARRAY; acpi_rman_mem.rm_descr = "ACPI I/O memory addresses"; if (rman_init(&acpi_rman_mem) != 0) panic("acpi rman_init memory failed"); /* Initialise the ACPI mutex */ mtx_init(&acpi_mutex, "ACPI global lock", NULL, MTX_DEF); /* * Set the globals from our tunables. This is needed because ACPI-CA * uses UINT8 for some values and we have no tunable_byte. */ AcpiGbl_EnableInterpreterSlack = acpi_interpreter_slack ? TRUE : FALSE; AcpiGbl_EnableAmlDebugObject = acpi_debug_objects ? TRUE : FALSE; AcpiGbl_UseDefaultRegisterWidths = acpi_ignore_reg_width ? TRUE : FALSE; #ifndef ACPI_DEBUG /* * Disable all debugging layers and levels. */ AcpiDbgLayer = 0; AcpiDbgLevel = 0; #endif /* Override OS interfaces if the user requested. */ acpi_reset_interfaces(dev); /* Load ACPI name space. */ status = AcpiLoadTables(); if (ACPI_FAILURE(status)) { device_printf(dev, "Could not load Namespace: %s\n", AcpiFormatException(status)); goto out; } #if defined(__i386__) || defined(__amd64__) /* Handle MCFG table if present. */ acpi_enable_pcie(); #endif /* * Note that some systems (specifically, those with namespace evaluation * issues that require the avoidance of parts of the namespace) must * avoid running _INI and _STA on everything, as well as dodging the final * object init pass. * * For these devices, we set ACPI_NO_DEVICE_INIT and ACPI_NO_OBJECT_INIT). * * XXX We should arrange for the object init pass after we have attached * all our child devices, but on many systems it works here. */ flags = 0; if (testenv("debug.acpi.avoid")) flags = ACPI_NO_DEVICE_INIT | ACPI_NO_OBJECT_INIT; /* Bring the hardware and basic handlers online. */ if (ACPI_FAILURE(status = AcpiEnableSubsystem(flags))) { device_printf(dev, "Could not enable ACPI: %s\n", AcpiFormatException(status)); goto out; } /* * Call the ECDT probe function to provide EC functionality before * the namespace has been evaluated. * * XXX This happens before the sysresource devices have been probed and * attached so its resources come from nexus0. In practice, this isn't * a problem but should be addressed eventually. */ acpi_ec_ecdt_probe(dev); /* Bring device objects and regions online. */ if (ACPI_FAILURE(status = AcpiInitializeObjects(flags))) { device_printf(dev, "Could not initialize ACPI objects: %s\n", AcpiFormatException(status)); goto out; } /* * Setup our sysctl tree. * * XXX: This doesn't check to make sure that none of these fail. */ sysctl_ctx_init(&sc->acpi_sysctl_ctx); sc->acpi_sysctl_tree = SYSCTL_ADD_NODE(&sc->acpi_sysctl_ctx, SYSCTL_STATIC_CHILDREN(_hw), OID_AUTO, device_get_name(dev), CTLFLAG_RD, 0, ""); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "supported_sleep_state", CTLTYPE_STRING | CTLFLAG_RD, 0, 0, acpi_supported_sleep_state_sysctl, "A", "List supported ACPI sleep states."); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "power_button_state", CTLTYPE_STRING | CTLFLAG_RW, &sc->acpi_power_button_sx, 0, acpi_sleep_state_sysctl, "A", "Power button ACPI sleep state."); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "sleep_button_state", CTLTYPE_STRING | CTLFLAG_RW, &sc->acpi_sleep_button_sx, 0, acpi_sleep_state_sysctl, "A", "Sleep button ACPI sleep state."); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "lid_switch_state", CTLTYPE_STRING | CTLFLAG_RW, &sc->acpi_lid_switch_sx, 0, acpi_sleep_state_sysctl, "A", "Lid ACPI sleep state. Set to S3 if you want to suspend your laptop when close the Lid."); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "standby_state", CTLTYPE_STRING | CTLFLAG_RW, &sc->acpi_standby_sx, 0, acpi_sleep_state_sysctl, "A", ""); SYSCTL_ADD_PROC(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "suspend_state", CTLTYPE_STRING | CTLFLAG_RW, &sc->acpi_suspend_sx, 0, acpi_sleep_state_sysctl, "A", ""); SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "sleep_delay", CTLFLAG_RW, &sc->acpi_sleep_delay, 0, "sleep delay in seconds"); SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "s4bios", CTLFLAG_RW, &sc->acpi_s4bios, 0, "S4BIOS mode"); SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "verbose", CTLFLAG_RW, &sc->acpi_verbose, 0, "verbose mode"); SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "disable_on_reboot", CTLFLAG_RW, &sc->acpi_do_disable, 0, "Disable ACPI when rebooting/halting system"); SYSCTL_ADD_INT(&sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(sc->acpi_sysctl_tree), OID_AUTO, "handle_reboot", CTLFLAG_RW, &sc->acpi_handle_reboot, 0, "Use ACPI Reset Register to reboot"); /* * Default to 1 second before sleeping to give some machines time to * stabilize. */ sc->acpi_sleep_delay = 1; if (bootverbose) sc->acpi_verbose = 1; if ((env = kern_getenv("hw.acpi.verbose")) != NULL) { if (strcmp(env, "0") != 0) sc->acpi_verbose = 1; freeenv(env); } /* Only enable reboot by default if the FADT says it is available. */ if (AcpiGbl_FADT.Flags & ACPI_FADT_RESET_REGISTER) sc->acpi_handle_reboot = 1; #if !ACPI_REDUCED_HARDWARE /* Only enable S4BIOS by default if the FACS says it is available. */ if (AcpiGbl_FACS != NULL && AcpiGbl_FACS->Flags & ACPI_FACS_S4_BIOS_PRESENT) sc->acpi_s4bios = 1; #endif /* Probe all supported sleep states. */ acpi_sleep_states[ACPI_STATE_S0] = TRUE; for (state = ACPI_STATE_S1; state < ACPI_S_STATE_COUNT; state++) if (ACPI_SUCCESS(AcpiEvaluateObject(ACPI_ROOT_OBJECT, __DECONST(char *, AcpiGbl_SleepStateNames[state]), NULL, NULL)) && ACPI_SUCCESS(AcpiGetSleepTypeData(state, &TypeA, &TypeB))) acpi_sleep_states[state] = TRUE; /* * Dispatch the default sleep state to devices. The lid switch is set * to UNKNOWN by default to avoid surprising users. */ sc->acpi_power_button_sx = acpi_sleep_states[ACPI_STATE_S5] ? ACPI_STATE_S5 : ACPI_STATE_UNKNOWN; sc->acpi_lid_switch_sx = ACPI_STATE_UNKNOWN; sc->acpi_standby_sx = acpi_sleep_states[ACPI_STATE_S1] ? ACPI_STATE_S1 : ACPI_STATE_UNKNOWN; sc->acpi_suspend_sx = acpi_sleep_states[ACPI_STATE_S3] ? ACPI_STATE_S3 : ACPI_STATE_UNKNOWN; /* Pick the first valid sleep state for the sleep button default. */ sc->acpi_sleep_button_sx = ACPI_STATE_UNKNOWN; for (state = ACPI_STATE_S1; state <= ACPI_STATE_S4; state++) if (acpi_sleep_states[state]) { sc->acpi_sleep_button_sx = state; break; } acpi_enable_fixed_events(sc); /* * Scan the namespace and attach/initialise children. */ /* Register our shutdown handler. */ EVENTHANDLER_REGISTER(shutdown_final, acpi_shutdown_final, sc, SHUTDOWN_PRI_LAST); /* * Register our acpi event handlers. * XXX should be configurable eg. via userland policy manager. */ EVENTHANDLER_REGISTER(acpi_sleep_event, acpi_system_eventhandler_sleep, sc, ACPI_EVENT_PRI_LAST); EVENTHANDLER_REGISTER(acpi_wakeup_event, acpi_system_eventhandler_wakeup, sc, ACPI_EVENT_PRI_LAST); /* Flag our initial states. */ sc->acpi_enabled = TRUE; sc->acpi_sstate = ACPI_STATE_S0; sc->acpi_sleep_disabled = TRUE; /* Create the control device */ sc->acpi_dev_t = make_dev(&acpi_cdevsw, 0, UID_ROOT, GID_OPERATOR, 0664, "acpi"); sc->acpi_dev_t->si_drv1 = sc; if ((error = acpi_machdep_init(dev))) goto out; /* Register ACPI again to pass the correct argument of pm_func. */ power_pm_register(POWER_PM_TYPE_ACPI, acpi_pm_func, sc); if (!acpi_disabled("bus")) { EVENTHANDLER_REGISTER(dev_lookup, acpi_lookup, NULL, 1000); acpi_probe_children(dev); } /* Update all GPEs and enable runtime GPEs. */ status = AcpiUpdateAllGpes(); if (ACPI_FAILURE(status)) device_printf(dev, "Could not update all GPEs: %s\n", AcpiFormatException(status)); /* Allow sleep request after a while. */ callout_init_mtx(&acpi_sleep_timer, &acpi_mutex, 0); callout_reset(&acpi_sleep_timer, hz * ACPI_MINIMUM_AWAKETIME, acpi_sleep_enable, sc); error = 0; out: return_VALUE (error); } static void acpi_set_power_children(device_t dev, int state) { device_t child; device_t *devlist; int dstate, i, numdevs; if (device_get_children(dev, &devlist, &numdevs) != 0) return; /* * Retrieve and set D-state for the sleep state if _SxD is present. * Skip children who aren't attached since they are handled separately. */ for (i = 0; i < numdevs; i++) { child = devlist[i]; dstate = state; if (device_is_attached(child) && acpi_device_pwr_for_sleep(dev, child, &dstate) == 0) acpi_set_powerstate(child, dstate); } free(devlist, M_TEMP); } static int acpi_suspend(device_t dev) { int error; GIANT_REQUIRED; error = bus_generic_suspend(dev); if (error == 0) acpi_set_power_children(dev, ACPI_STATE_D3); return (error); } static int acpi_resume(device_t dev) { GIANT_REQUIRED; acpi_set_power_children(dev, ACPI_STATE_D0); return (bus_generic_resume(dev)); } static int acpi_shutdown(device_t dev) { GIANT_REQUIRED; /* Allow children to shutdown first. */ bus_generic_shutdown(dev); /* * Enable any GPEs that are able to power-on the system (i.e., RTC). * Also, disable any that are not valid for this state (most). */ acpi_wake_prep_walk(ACPI_STATE_S5); return (0); } /* * Handle a new device being added */ static device_t acpi_add_child(device_t bus, u_int order, const char *name, int unit) { struct acpi_device *ad; device_t child; if ((ad = malloc(sizeof(*ad), M_ACPIDEV, M_NOWAIT | M_ZERO)) == NULL) return (NULL); resource_list_init(&ad->ad_rl); child = device_add_child_ordered(bus, order, name, unit); if (child != NULL) device_set_ivars(child, ad); else free(ad, M_ACPIDEV); return (child); } static int acpi_print_child(device_t bus, device_t child) { struct acpi_device *adev = device_get_ivars(child); struct resource_list *rl = &adev->ad_rl; int retval = 0; retval += bus_print_child_header(bus, child); retval += resource_list_print_type(rl, "port", SYS_RES_IOPORT, "%#jx"); retval += resource_list_print_type(rl, "iomem", SYS_RES_MEMORY, "%#jx"); retval += resource_list_print_type(rl, "irq", SYS_RES_IRQ, "%jd"); retval += resource_list_print_type(rl, "drq", SYS_RES_DRQ, "%jd"); if (device_get_flags(child)) retval += printf(" flags %#x", device_get_flags(child)); retval += bus_print_child_domain(bus, child); retval += bus_print_child_footer(bus, child); return (retval); } /* * If this device is an ACPI child but no one claimed it, attempt * to power it off. We'll power it back up when a driver is added. * * XXX Disabled for now since many necessary devices (like fdc and * ATA) don't claim the devices we created for them but still expect * them to be powered up. */ static void acpi_probe_nomatch(device_t bus, device_t child) { #ifdef ACPI_ENABLE_POWERDOWN_NODRIVER acpi_set_powerstate(child, ACPI_STATE_D3); #endif } /* * If a new driver has a chance to probe a child, first power it up. * * XXX Disabled for now (see acpi_probe_nomatch for details). */ static void acpi_driver_added(device_t dev, driver_t *driver) { device_t child, *devlist; int i, numdevs; DEVICE_IDENTIFY(driver, dev); if (device_get_children(dev, &devlist, &numdevs)) return; for (i = 0; i < numdevs; i++) { child = devlist[i]; if (device_get_state(child) == DS_NOTPRESENT) { #ifdef ACPI_ENABLE_POWERDOWN_NODRIVER acpi_set_powerstate(child, ACPI_STATE_D0); if (device_probe_and_attach(child) != 0) acpi_set_powerstate(child, ACPI_STATE_D3); #else device_probe_and_attach(child); #endif } } free(devlist, M_TEMP); } /* Location hint for devctl(8) */ static int acpi_child_location_str_method(device_t cbdev, device_t child, char *buf, size_t buflen) { struct acpi_device *dinfo = device_get_ivars(child); char buf2[32]; int pxm; if (dinfo->ad_handle) { snprintf(buf, buflen, "handle=%s", acpi_name(dinfo->ad_handle)); if (ACPI_SUCCESS(acpi_GetInteger(dinfo->ad_handle, "_PXM", &pxm))) { snprintf(buf2, 32, " _PXM=%d", pxm); strlcat(buf, buf2, buflen); } } else { snprintf(buf, buflen, "unknown"); } return (0); } /* PnP information for devctl(8) */ static int acpi_child_pnpinfo_str_method(device_t cbdev, device_t child, char *buf, size_t buflen) { struct acpi_device *dinfo = device_get_ivars(child); ACPI_DEVICE_INFO *adinfo; if (ACPI_FAILURE(AcpiGetObjectInfo(dinfo->ad_handle, &adinfo))) { snprintf(buf, buflen, "unknown"); return (0); } snprintf(buf, buflen, "_HID=%s _UID=%lu", (adinfo->Valid & ACPI_VALID_HID) ? adinfo->HardwareId.String : "none", (adinfo->Valid & ACPI_VALID_UID) ? strtoul(adinfo->UniqueId.String, NULL, 10) : 0UL); AcpiOsFree(adinfo); return (0); } /* * Handle per-device ivars */ static int acpi_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { struct acpi_device *ad; if ((ad = device_get_ivars(child)) == NULL) { device_printf(child, "device has no ivars\n"); return (ENOENT); } /* ACPI and ISA compatibility ivars */ switch(index) { case ACPI_IVAR_HANDLE: *(ACPI_HANDLE *)result = ad->ad_handle; break; case ACPI_IVAR_PRIVATE: *(void **)result = ad->ad_private; break; case ACPI_IVAR_FLAGS: *(int *)result = ad->ad_flags; break; case ISA_IVAR_VENDORID: case ISA_IVAR_SERIAL: case ISA_IVAR_COMPATID: *(int *)result = -1; break; case ISA_IVAR_LOGICALID: *(int *)result = acpi_isa_get_logicalid(child); break; case PCI_IVAR_CLASS: *(uint8_t*)result = (ad->ad_cls_class >> 16) & 0xff; break; case PCI_IVAR_SUBCLASS: *(uint8_t*)result = (ad->ad_cls_class >> 8) & 0xff; break; case PCI_IVAR_PROGIF: *(uint8_t*)result = (ad->ad_cls_class >> 0) & 0xff; break; default: return (ENOENT); } return (0); } static int acpi_write_ivar(device_t dev, device_t child, int index, uintptr_t value) { struct acpi_device *ad; if ((ad = device_get_ivars(child)) == NULL) { device_printf(child, "device has no ivars\n"); return (ENOENT); } switch(index) { case ACPI_IVAR_HANDLE: ad->ad_handle = (ACPI_HANDLE)value; break; case ACPI_IVAR_PRIVATE: ad->ad_private = (void *)value; break; case ACPI_IVAR_FLAGS: ad->ad_flags = (int)value; break; default: panic("bad ivar write request (%d)", index); return (ENOENT); } return (0); } /* * Handle child resource allocation/removal */ static struct resource_list * acpi_get_rlist(device_t dev, device_t child) { struct acpi_device *ad; ad = device_get_ivars(child); return (&ad->ad_rl); } static int acpi_match_resource_hint(device_t dev, int type, long value) { struct acpi_device *ad = device_get_ivars(dev); struct resource_list *rl = &ad->ad_rl; struct resource_list_entry *rle; STAILQ_FOREACH(rle, rl, link) { if (rle->type != type) continue; if (rle->start <= value && rle->end >= value) return (1); } return (0); } /* * Wire device unit numbers based on resource matches in hints. */ static void acpi_hint_device_unit(device_t acdev, device_t child, const char *name, int *unitp) { const char *s; long value; int line, matches, unit; /* * Iterate over all the hints for the devices with the specified * name to see if one's resources are a subset of this device. */ line = 0; while (resource_find_dev(&line, name, &unit, "at", NULL) == 0) { /* Must have an "at" for acpi or isa. */ resource_string_value(name, unit, "at", &s); if (!(strcmp(s, "acpi0") == 0 || strcmp(s, "acpi") == 0 || strcmp(s, "isa0") == 0 || strcmp(s, "isa") == 0)) continue; /* * Check for matching resources. We must have at least one match. * Since I/O and memory resources cannot be shared, if we get a * match on either of those, ignore any mismatches in IRQs or DRQs. * * XXX: We may want to revisit this to be more lenient and wire * as long as it gets one match. */ matches = 0; if (resource_long_value(name, unit, "port", &value) == 0) { /* * Floppy drive controllers are notorious for having a * wide variety of resources not all of which include the * first port that is specified by the hint (typically * 0x3f0) (see the comment above fdc_isa_alloc_resources() * in fdc_isa.c). However, they do all seem to include * port + 2 (e.g. 0x3f2) so for a floppy device, look for * 'value + 2' in the port resources instead of the hint * value. */ if (strcmp(name, "fdc") == 0) value += 2; if (acpi_match_resource_hint(child, SYS_RES_IOPORT, value)) matches++; else continue; } if (resource_long_value(name, unit, "maddr", &value) == 0) { if (acpi_match_resource_hint(child, SYS_RES_MEMORY, value)) matches++; else continue; } if (matches > 0) goto matched; if (resource_long_value(name, unit, "irq", &value) == 0) { if (acpi_match_resource_hint(child, SYS_RES_IRQ, value)) matches++; else continue; } if (resource_long_value(name, unit, "drq", &value) == 0) { if (acpi_match_resource_hint(child, SYS_RES_DRQ, value)) matches++; else continue; } matched: if (matches > 0) { /* We have a winner! */ *unitp = unit; break; } } } /* * Fetch the NUMA domain for a device by mapping the value returned by * _PXM to a NUMA domain. If the device does not have a _PXM method, * -2 is returned. If any other error occurs, -1 is returned. */ static int acpi_parse_pxm(device_t dev) { #ifdef NUMA #if defined(__i386__) || defined(__amd64__) ACPI_HANDLE handle; ACPI_STATUS status; int pxm; handle = acpi_get_handle(dev); if (handle == NULL) return (-2); status = acpi_GetInteger(handle, "_PXM", &pxm); if (ACPI_SUCCESS(status)) return (acpi_map_pxm_to_vm_domainid(pxm)); if (status == AE_NOT_FOUND) return (-2); #endif #endif return (-1); } int acpi_get_cpus(device_t dev, device_t child, enum cpu_sets op, size_t setsize, cpuset_t *cpuset) { int d, error; d = acpi_parse_pxm(child); if (d < 0) return (bus_generic_get_cpus(dev, child, op, setsize, cpuset)); switch (op) { case LOCAL_CPUS: if (setsize != sizeof(cpuset_t)) return (EINVAL); *cpuset = cpuset_domain[d]; return (0); case INTR_CPUS: error = bus_generic_get_cpus(dev, child, op, setsize, cpuset); if (error != 0) return (error); if (setsize != sizeof(cpuset_t)) return (EINVAL); CPU_AND(cpuset, &cpuset_domain[d]); return (0); default: return (bus_generic_get_cpus(dev, child, op, setsize, cpuset)); } } /* * Fetch the NUMA domain for the given device 'dev'. * * If a device has a _PXM method, map that to a NUMA domain. * Otherwise, pass the request up to the parent. * If there's no matching domain or the domain cannot be * determined, return ENOENT. */ int acpi_get_domain(device_t dev, device_t child, int *domain) { int d; d = acpi_parse_pxm(child); if (d >= 0) { *domain = d; return (0); } if (d == -1) return (ENOENT); /* No _PXM node; go up a level */ return (bus_generic_get_domain(dev, child, domain)); } /* * Pre-allocate/manage all memory and IO resources. Since rman can't handle * duplicates, we merge any in the sysresource attach routine. */ static int acpi_sysres_alloc(device_t dev) { struct resource *res; struct resource_list *rl; struct resource_list_entry *rle; struct rman *rm; device_t *children; int child_count, i; /* * Probe/attach any sysresource devices. This would be unnecessary if we * had multi-pass probe/attach. */ if (device_get_children(dev, &children, &child_count) != 0) return (ENXIO); for (i = 0; i < child_count; i++) { - if (ACPI_ID_PROBE(dev, children[i], sysres_ids) != NULL) + if (ACPI_ID_PROBE(dev, children[i], sysres_ids, NULL) <= 0) device_probe_and_attach(children[i]); } free(children, M_TEMP); rl = BUS_GET_RESOURCE_LIST(device_get_parent(dev), dev); STAILQ_FOREACH(rle, rl, link) { if (rle->res != NULL) { device_printf(dev, "duplicate resource for %jx\n", rle->start); continue; } /* Only memory and IO resources are valid here. */ switch (rle->type) { case SYS_RES_IOPORT: rm = &acpi_rman_io; break; case SYS_RES_MEMORY: rm = &acpi_rman_mem; break; default: continue; } /* Pre-allocate resource and add to our rman pool. */ res = BUS_ALLOC_RESOURCE(device_get_parent(dev), dev, rle->type, &rle->rid, rle->start, rle->start + rle->count - 1, rle->count, 0); if (res != NULL) { rman_manage_region(rm, rman_get_start(res), rman_get_end(res)); rle->res = res; } else if (bootverbose) device_printf(dev, "reservation of %jx, %jx (%d) failed\n", rle->start, rle->count, rle->type); } return (0); } /* * Reserve declared resources for devices found during attach once system * resources have been allocated. */ static void acpi_reserve_resources(device_t dev) { struct resource_list_entry *rle; struct resource_list *rl; struct acpi_device *ad; struct acpi_softc *sc; device_t *children; int child_count, i; sc = device_get_softc(dev); if (device_get_children(dev, &children, &child_count) != 0) return; for (i = 0; i < child_count; i++) { ad = device_get_ivars(children[i]); rl = &ad->ad_rl; /* Don't reserve system resources. */ - if (ACPI_ID_PROBE(dev, children[i], sysres_ids) != NULL) + if (ACPI_ID_PROBE(dev, children[i], sysres_ids, NULL) <= 0) continue; STAILQ_FOREACH(rle, rl, link) { /* * Don't reserve IRQ resources. There are many sticky things * to get right otherwise (e.g. IRQs for psm, atkbd, and HPET * when using legacy routing). */ if (rle->type == SYS_RES_IRQ) continue; /* * Don't reserve the resource if it is already allocated. * The acpi_ec(4) driver can allocate its resources early * if ECDT is present. */ if (rle->res != NULL) continue; /* * Try to reserve the resource from our parent. If this * fails because the resource is a system resource, just * let it be. The resource range is already reserved so * that other devices will not use it. If the driver * needs to allocate the resource, then * acpi_alloc_resource() will sub-alloc from the system * resource. */ resource_list_reserve(rl, dev, children[i], rle->type, &rle->rid, rle->start, rle->end, rle->count, 0); } } free(children, M_TEMP); sc->acpi_resources_reserved = 1; } static int acpi_set_resource(device_t dev, device_t child, int type, int rid, rman_res_t start, rman_res_t count) { struct acpi_softc *sc = device_get_softc(dev); struct acpi_device *ad = device_get_ivars(child); struct resource_list *rl = &ad->ad_rl; #if defined(__i386__) || defined(__amd64__) ACPI_DEVICE_INFO *devinfo; #endif rman_res_t end; /* Ignore IRQ resources for PCI link devices. */ - if (type == SYS_RES_IRQ && ACPI_ID_PROBE(dev, child, pcilink_ids) != NULL) + if (type == SYS_RES_IRQ && + ACPI_ID_PROBE(dev, child, pcilink_ids, NULL) <= 0) return (0); /* * Ignore most resources for PCI root bridges. Some BIOSes * incorrectly enumerate the memory ranges they decode as plain * memory resources instead of as ResourceProducer ranges. Other * BIOSes incorrectly list system resource entries for I/O ranges * under the PCI bridge. Do allow the one known-correct case on * x86 of a PCI bridge claiming the I/O ports used for PCI config * access. */ #if defined(__i386__) || defined(__amd64__) if (type == SYS_RES_MEMORY || type == SYS_RES_IOPORT) { if (ACPI_SUCCESS(AcpiGetObjectInfo(ad->ad_handle, &devinfo))) { if ((devinfo->Flags & ACPI_PCI_ROOT_BRIDGE) != 0) { if (!(type == SYS_RES_IOPORT && start == CONF1_ADDR_PORT)) { AcpiOsFree(devinfo); return (0); } } AcpiOsFree(devinfo); } } #endif /* If the resource is already allocated, fail. */ if (resource_list_busy(rl, type, rid)) return (EBUSY); /* If the resource is already reserved, release it. */ if (resource_list_reserved(rl, type, rid)) resource_list_unreserve(rl, dev, child, type, rid); /* Add the resource. */ end = (start + count - 1); resource_list_add(rl, type, rid, start, end, count); /* Don't reserve resources until the system resources are allocated. */ if (!sc->acpi_resources_reserved) return (0); /* Don't reserve system resources. */ - if (ACPI_ID_PROBE(dev, child, sysres_ids) != NULL) + if (ACPI_ID_PROBE(dev, child, sysres_ids, NULL) <= 0) return (0); /* * Don't reserve IRQ resources. There are many sticky things to * get right otherwise (e.g. IRQs for psm, atkbd, and HPET when * using legacy routing). */ if (type == SYS_RES_IRQ) return (0); /* * Reserve the resource. * * XXX: Ignores failure for now. Failure here is probably a * BIOS/firmware bug? */ resource_list_reserve(rl, dev, child, type, &rid, start, end, count, 0); return (0); } static struct resource * acpi_alloc_resource(device_t bus, device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { #ifndef INTRNG ACPI_RESOURCE ares; #endif struct acpi_device *ad; struct resource_list_entry *rle; struct resource_list *rl; struct resource *res; int isdefault = RMAN_IS_DEFAULT_RANGE(start, end); /* * First attempt at allocating the resource. For direct children, * use resource_list_alloc() to handle reserved resources. For * other devices, pass the request up to our parent. */ if (bus == device_get_parent(child)) { ad = device_get_ivars(child); rl = &ad->ad_rl; /* * Simulate the behavior of the ISA bus for direct children * devices. That is, if a non-default range is specified for * a resource that doesn't exist, use bus_set_resource() to * add the resource before allocating it. Note that these * resources will not be reserved. */ if (!isdefault && resource_list_find(rl, type, *rid) == NULL) resource_list_add(rl, type, *rid, start, end, count); res = resource_list_alloc(rl, bus, child, type, rid, start, end, count, flags); #ifndef INTRNG if (res != NULL && type == SYS_RES_IRQ) { /* * Since bus_config_intr() takes immediate effect, we cannot * configure the interrupt associated with a device when we * parse the resources but have to defer it until a driver * actually allocates the interrupt via bus_alloc_resource(). * * XXX: Should we handle the lookup failing? */ if (ACPI_SUCCESS(acpi_lookup_irq_resource(child, *rid, res, &ares))) acpi_config_intr(child, &ares); } #endif /* * If this is an allocation of the "default" range for a given * RID, fetch the exact bounds for this resource from the * resource list entry to try to allocate the range from the * system resource regions. */ if (res == NULL && isdefault) { rle = resource_list_find(rl, type, *rid); if (rle != NULL) { start = rle->start; end = rle->end; count = rle->count; } } } else res = BUS_ALLOC_RESOURCE(device_get_parent(bus), child, type, rid, start, end, count, flags); /* * If the first attempt failed and this is an allocation of a * specific range, try to satisfy the request via a suballocation * from our system resource regions. */ if (res == NULL && start + count - 1 == end) res = acpi_alloc_sysres(child, type, rid, start, end, count, flags); return (res); } /* * Attempt to allocate a specific resource range from the system * resource ranges. Note that we only handle memory and I/O port * system resources. */ struct resource * acpi_alloc_sysres(device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags) { struct rman *rm; struct resource *res; switch (type) { case SYS_RES_IOPORT: rm = &acpi_rman_io; break; case SYS_RES_MEMORY: rm = &acpi_rman_mem; break; default: return (NULL); } KASSERT(start + count - 1 == end, ("wildcard resource range")); res = rman_reserve_resource(rm, start, end, count, flags & ~RF_ACTIVE, child); if (res == NULL) return (NULL); rman_set_rid(res, *rid); /* If requested, activate the resource using the parent's method. */ if (flags & RF_ACTIVE) if (bus_activate_resource(child, type, *rid, res) != 0) { rman_release_resource(res); return (NULL); } return (res); } static int acpi_is_resource_managed(int type, struct resource *r) { /* We only handle memory and IO resources through rman. */ switch (type) { case SYS_RES_IOPORT: return (rman_is_region_manager(r, &acpi_rman_io)); case SYS_RES_MEMORY: return (rman_is_region_manager(r, &acpi_rman_mem)); } return (0); } static int acpi_adjust_resource(device_t bus, device_t child, int type, struct resource *r, rman_res_t start, rman_res_t end) { if (acpi_is_resource_managed(type, r)) return (rman_adjust_resource(r, start, end)); return (bus_generic_adjust_resource(bus, child, type, r, start, end)); } static int acpi_release_resource(device_t bus, device_t child, int type, int rid, struct resource *r) { int ret; /* * If this resource belongs to one of our internal managers, * deactivate it and release it to the local pool. */ if (acpi_is_resource_managed(type, r)) { if (rman_get_flags(r) & RF_ACTIVE) { ret = bus_deactivate_resource(child, type, rid, r); if (ret != 0) return (ret); } return (rman_release_resource(r)); } return (bus_generic_rl_release_resource(bus, child, type, rid, r)); } static void acpi_delete_resource(device_t bus, device_t child, int type, int rid) { struct resource_list *rl; rl = acpi_get_rlist(bus, child); if (resource_list_busy(rl, type, rid)) { device_printf(bus, "delete_resource: Resource still owned by child" " (type=%d, rid=%d)\n", type, rid); return; } resource_list_unreserve(rl, bus, child, type, rid); resource_list_delete(rl, type, rid); } /* Allocate an IO port or memory resource, given its GAS. */ int acpi_bus_alloc_gas(device_t dev, int *type, int *rid, ACPI_GENERIC_ADDRESS *gas, struct resource **res, u_int flags) { int error, res_type; error = ENOMEM; if (type == NULL || rid == NULL || gas == NULL || res == NULL) return (EINVAL); /* We only support memory and IO spaces. */ switch (gas->SpaceId) { case ACPI_ADR_SPACE_SYSTEM_MEMORY: res_type = SYS_RES_MEMORY; break; case ACPI_ADR_SPACE_SYSTEM_IO: res_type = SYS_RES_IOPORT; break; default: return (EOPNOTSUPP); } /* * If the register width is less than 8, assume the BIOS author means * it is a bit field and just allocate a byte. */ if (gas->BitWidth && gas->BitWidth < 8) gas->BitWidth = 8; /* Validate the address after we're sure we support the space. */ if (gas->Address == 0 || gas->BitWidth == 0) return (EINVAL); bus_set_resource(dev, res_type, *rid, gas->Address, gas->BitWidth / 8); *res = bus_alloc_resource_any(dev, res_type, rid, RF_ACTIVE | flags); if (*res != NULL) { *type = res_type; error = 0; } else bus_delete_resource(dev, res_type, *rid); return (error); } /* Probe _HID and _CID for compatible ISA PNP ids. */ static uint32_t acpi_isa_get_logicalid(device_t dev) { ACPI_DEVICE_INFO *devinfo; ACPI_HANDLE h; uint32_t pnpid; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* Fetch and validate the HID. */ if ((h = acpi_get_handle(dev)) == NULL || ACPI_FAILURE(AcpiGetObjectInfo(h, &devinfo))) return_VALUE (0); pnpid = (devinfo->Valid & ACPI_VALID_HID) != 0 && devinfo->HardwareId.Length >= ACPI_EISAID_STRING_SIZE ? PNP_EISAID(devinfo->HardwareId.String) : 0; AcpiOsFree(devinfo); return_VALUE (pnpid); } static int acpi_isa_get_compatid(device_t dev, uint32_t *cids, int count) { ACPI_DEVICE_INFO *devinfo; ACPI_PNP_DEVICE_ID *ids; ACPI_HANDLE h; uint32_t *pnpid; int i, valid; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); pnpid = cids; /* Fetch and validate the CID */ if ((h = acpi_get_handle(dev)) == NULL || ACPI_FAILURE(AcpiGetObjectInfo(h, &devinfo))) return_VALUE (0); if ((devinfo->Valid & ACPI_VALID_CID) == 0) { AcpiOsFree(devinfo); return_VALUE (0); } if (devinfo->CompatibleIdList.Count < count) count = devinfo->CompatibleIdList.Count; ids = devinfo->CompatibleIdList.Ids; for (i = 0, valid = 0; i < count; i++) if (ids[i].Length >= ACPI_EISAID_STRING_SIZE && strncmp(ids[i].String, "PNP", 3) == 0) { *pnpid++ = PNP_EISAID(ids[i].String); valid++; } AcpiOsFree(devinfo); return_VALUE (valid); } -static char * -acpi_device_id_probe(device_t bus, device_t dev, char **ids) +static int +acpi_device_id_probe(device_t bus, device_t dev, char **ids, char **match) { ACPI_HANDLE h; ACPI_OBJECT_TYPE t; + int rv; int i; h = acpi_get_handle(dev); if (ids == NULL || h == NULL) - return (NULL); + return (ENXIO); t = acpi_get_type(dev); if (t != ACPI_TYPE_DEVICE && t != ACPI_TYPE_PROCESSOR) - return (NULL); + return (ENXIO); /* Try to match one of the array of IDs with a HID or CID. */ for (i = 0; ids[i] != NULL; i++) { - if (acpi_MatchHid(h, ids[i])) - return (ids[i]); + rv = acpi_MatchHid(h, ids[i]); + if (rv == ACPI_MATCHHID_NOMATCH) + continue; + + if (match != NULL) { + *match = ids[i]; + } + return ((rv == ACPI_MATCHHID_HID)? + BUS_PROBE_DEFAULT : BUS_PROBE_LOW_PRIORITY); } - return (NULL); + return (ENXIO); } static ACPI_STATUS acpi_device_eval_obj(device_t bus, device_t dev, ACPI_STRING pathname, ACPI_OBJECT_LIST *parameters, ACPI_BUFFER *ret) { ACPI_HANDLE h; if (dev == NULL) h = ACPI_ROOT_OBJECT; else if ((h = acpi_get_handle(dev)) == NULL) return (AE_BAD_PARAMETER); return (AcpiEvaluateObject(h, pathname, parameters, ret)); } int acpi_device_pwr_for_sleep(device_t bus, device_t dev, int *dstate) { struct acpi_softc *sc; ACPI_HANDLE handle; ACPI_STATUS status; char sxd[8]; handle = acpi_get_handle(dev); /* * XXX If we find these devices, don't try to power them down. * The serial and IRDA ports on my T23 hang the system when * set to D3 and it appears that such legacy devices may * need special handling in their drivers. */ if (dstate == NULL || handle == NULL || acpi_MatchHid(handle, "PNP0500") || acpi_MatchHid(handle, "PNP0501") || acpi_MatchHid(handle, "PNP0502") || acpi_MatchHid(handle, "PNP0510") || acpi_MatchHid(handle, "PNP0511")) return (ENXIO); /* * Override next state with the value from _SxD, if present. * Note illegal _S0D is evaluated because some systems expect this. */ sc = device_get_softc(bus); snprintf(sxd, sizeof(sxd), "_S%dD", sc->acpi_sstate); status = acpi_GetInteger(handle, sxd, dstate); if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { device_printf(dev, "failed to get %s on %s: %s\n", sxd, acpi_name(handle), AcpiFormatException(status)); return (ENXIO); } return (0); } /* Callback arg for our implementation of walking the namespace. */ struct acpi_device_scan_ctx { acpi_scan_cb_t user_fn; void *arg; ACPI_HANDLE parent; }; static ACPI_STATUS acpi_device_scan_cb(ACPI_HANDLE h, UINT32 level, void *arg, void **retval) { struct acpi_device_scan_ctx *ctx; device_t dev, old_dev; ACPI_STATUS status; ACPI_OBJECT_TYPE type; /* * Skip this device if we think we'll have trouble with it or it is * the parent where the scan began. */ ctx = (struct acpi_device_scan_ctx *)arg; if (acpi_avoid(h) || h == ctx->parent) return (AE_OK); /* If this is not a valid device type (e.g., a method), skip it. */ if (ACPI_FAILURE(AcpiGetType(h, &type))) return (AE_OK); if (type != ACPI_TYPE_DEVICE && type != ACPI_TYPE_PROCESSOR && type != ACPI_TYPE_THERMAL && type != ACPI_TYPE_POWER) return (AE_OK); /* * Call the user function with the current device. If it is unchanged * afterwards, return. Otherwise, we update the handle to the new dev. */ old_dev = acpi_get_device(h); dev = old_dev; status = ctx->user_fn(h, &dev, level, ctx->arg); if (ACPI_FAILURE(status) || old_dev == dev) return (status); /* Remove the old child and its connection to the handle. */ if (old_dev != NULL) { device_delete_child(device_get_parent(old_dev), old_dev); AcpiDetachData(h, acpi_fake_objhandler); } /* Recreate the handle association if the user created a device. */ if (dev != NULL) AcpiAttachData(h, acpi_fake_objhandler, dev); return (AE_OK); } static ACPI_STATUS acpi_device_scan_children(device_t bus, device_t dev, int max_depth, acpi_scan_cb_t user_fn, void *arg) { ACPI_HANDLE h; struct acpi_device_scan_ctx ctx; if (acpi_disabled("children")) return (AE_OK); if (dev == NULL) h = ACPI_ROOT_OBJECT; else if ((h = acpi_get_handle(dev)) == NULL) return (AE_BAD_PARAMETER); ctx.user_fn = user_fn; ctx.arg = arg; ctx.parent = h; return (AcpiWalkNamespace(ACPI_TYPE_ANY, h, max_depth, acpi_device_scan_cb, NULL, &ctx, NULL)); } /* * Even though ACPI devices are not PCI, we use the PCI approach for setting * device power states since it's close enough to ACPI. */ static int acpi_set_powerstate(device_t child, int state) { ACPI_HANDLE h; ACPI_STATUS status; h = acpi_get_handle(child); if (state < ACPI_STATE_D0 || state > ACPI_D_STATES_MAX) return (EINVAL); if (h == NULL) return (0); /* Ignore errors if the power methods aren't present. */ status = acpi_pwr_switch_consumer(h, state); if (ACPI_SUCCESS(status)) { if (bootverbose) device_printf(child, "set ACPI power state D%d on %s\n", state, acpi_name(h)); } else if (status != AE_NOT_FOUND) device_printf(child, "failed to set ACPI power state D%d on %s: %s\n", state, acpi_name(h), AcpiFormatException(status)); return (0); } static int acpi_isa_pnp_probe(device_t bus, device_t child, struct isa_pnp_id *ids) { int result, cid_count, i; uint32_t lid, cids[8]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* * ISA-style drivers attached to ACPI may persist and * probe manually if we return ENOENT. We never want * that to happen, so don't ever return it. */ result = ENXIO; /* Scan the supplied IDs for a match */ lid = acpi_isa_get_logicalid(child); cid_count = acpi_isa_get_compatid(child, cids, 8); while (ids && ids->ip_id) { if (lid == ids->ip_id) { result = 0; goto out; } for (i = 0; i < cid_count; i++) { if (cids[i] == ids->ip_id) { result = 0; goto out; } } ids++; } out: if (result == 0 && ids->ip_desc) device_set_desc(child, ids->ip_desc); return_VALUE (result); } #if defined(__i386__) || defined(__amd64__) /* * Look for a MCFG table. If it is present, use the settings for * domain (segment) 0 to setup PCI config space access via the memory * map. */ static void acpi_enable_pcie(void) { ACPI_TABLE_HEADER *hdr; ACPI_MCFG_ALLOCATION *alloc, *end; ACPI_STATUS status; status = AcpiGetTable(ACPI_SIG_MCFG, 1, &hdr); if (ACPI_FAILURE(status)) return; end = (ACPI_MCFG_ALLOCATION *)((char *)hdr + hdr->Length); alloc = (ACPI_MCFG_ALLOCATION *)((ACPI_TABLE_MCFG *)hdr + 1); while (alloc < end) { if (alloc->PciSegment == 0) { pcie_cfgregopen(alloc->Address, alloc->StartBusNumber, alloc->EndBusNumber); return; } alloc++; } } #elif defined(__aarch64__) static void acpi_enable_pcie(device_t child, int segment) { ACPI_TABLE_HEADER *hdr; ACPI_MCFG_ALLOCATION *alloc, *end; ACPI_STATUS status; status = AcpiGetTable(ACPI_SIG_MCFG, 1, &hdr); if (ACPI_FAILURE(status)) return; end = (ACPI_MCFG_ALLOCATION *)((char *)hdr + hdr->Length); alloc = (ACPI_MCFG_ALLOCATION *)((ACPI_TABLE_MCFG *)hdr + 1); while (alloc < end) { if (alloc->PciSegment == segment) { bus_set_resource(child, SYS_RES_MEMORY, 0, alloc->Address, 0x10000000); return; } alloc++; } } #endif /* * Scan all of the ACPI namespace and attach child devices. * * We should only expect to find devices in the \_PR, \_TZ, \_SI, and * \_SB scopes, and \_PR and \_TZ became obsolete in the ACPI 2.0 spec. * However, in violation of the spec, some systems place their PCI link * devices in \, so we have to walk the whole namespace. We check the * type of namespace nodes, so this should be ok. */ static void acpi_probe_children(device_t bus) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* * Scan the namespace and insert placeholders for all the devices that * we find. We also probe/attach any early devices. * * Note that we use AcpiWalkNamespace rather than AcpiGetDevices because * we want to create nodes for all devices, not just those that are * currently present. (This assumes that we don't want to create/remove * devices as they appear, which might be smarter.) */ ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "namespace scan\n")); AcpiWalkNamespace(ACPI_TYPE_ANY, ACPI_ROOT_OBJECT, 100, acpi_probe_child, NULL, bus, NULL); /* Pre-allocate resources for our rman from any sysresource devices. */ acpi_sysres_alloc(bus); /* Reserve resources already allocated to children. */ acpi_reserve_resources(bus); /* Create any static children by calling device identify methods. */ ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "device identify routines\n")); bus_generic_probe(bus); /* Probe/attach all children, created statically and from the namespace. */ ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "acpi bus_generic_attach\n")); bus_generic_attach(bus); /* Attach wake sysctls. */ acpi_wake_sysctl_walk(bus); ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "done attaching children\n")); return_VOID; } /* * Determine the probe order for a given device. */ static void acpi_probe_order(ACPI_HANDLE handle, int *order) { ACPI_OBJECT_TYPE type; /* * 0. CPUs * 1. I/O port and memory system resource holders * 2. Clocks and timers (to handle early accesses) * 3. Embedded controllers (to handle early accesses) * 4. PCI Link Devices */ AcpiGetType(handle, &type); if (type == ACPI_TYPE_PROCESSOR) *order = 0; else if (acpi_MatchHid(handle, "PNP0C01") || acpi_MatchHid(handle, "PNP0C02")) *order = 1; else if (acpi_MatchHid(handle, "PNP0100") || acpi_MatchHid(handle, "PNP0103") || acpi_MatchHid(handle, "PNP0B00")) *order = 2; else if (acpi_MatchHid(handle, "PNP0C09")) *order = 3; else if (acpi_MatchHid(handle, "PNP0C0F")) *order = 4; } /* * Evaluate a child device and determine whether we might attach a device to * it. */ static ACPI_STATUS acpi_probe_child(ACPI_HANDLE handle, UINT32 level, void *context, void **status) { ACPI_DEVICE_INFO *devinfo; struct acpi_device *ad; #ifdef __aarch64__ int segment; #endif struct acpi_prw_data prw; ACPI_OBJECT_TYPE type; ACPI_HANDLE h; device_t bus, child; char *handle_str; int order; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (acpi_disabled("children")) return_ACPI_STATUS (AE_OK); /* Skip this device if we think we'll have trouble with it. */ if (acpi_avoid(handle)) return_ACPI_STATUS (AE_OK); bus = (device_t)context; if (ACPI_SUCCESS(AcpiGetType(handle, &type))) { handle_str = acpi_name(handle); switch (type) { case ACPI_TYPE_DEVICE: /* * Since we scan from \, be sure to skip system scope objects. * \_SB_ and \_TZ_ are defined in ACPICA as devices to work around * BIOS bugs. For example, \_SB_ is to allow \_SB_._INI to be run * during the initialization and \_TZ_ is to support Notify() on it. */ if (strcmp(handle_str, "\\_SB_") == 0 || strcmp(handle_str, "\\_TZ_") == 0) break; if (acpi_parse_prw(handle, &prw) == 0) AcpiSetupGpeForWake(handle, prw.gpe_handle, prw.gpe_bit); /* * Ignore devices that do not have a _HID or _CID. They should * be discovered by other buses (e.g. the PCI bus driver). */ if (!acpi_has_hid(handle)) break; /* FALLTHROUGH */ case ACPI_TYPE_PROCESSOR: case ACPI_TYPE_THERMAL: case ACPI_TYPE_POWER: /* * Create a placeholder device for this node. Sort the * placeholder so that the probe/attach passes will run * breadth-first. Orders less than ACPI_DEV_BASE_ORDER * are reserved for special objects (i.e., system * resources). */ ACPI_DEBUG_PRINT((ACPI_DB_OBJECTS, "scanning '%s'\n", handle_str)); order = level * 10 + ACPI_DEV_BASE_ORDER; acpi_probe_order(handle, &order); child = BUS_ADD_CHILD(bus, order, NULL, -1); if (child == NULL) break; /* Associate the handle with the device_t and vice versa. */ acpi_set_handle(child, handle); AcpiAttachData(handle, acpi_fake_objhandler, child); /* * Check that the device is present. If it's not present, * leave it disabled (so that we have a device_t attached to * the handle, but we don't probe it). * * XXX PCI link devices sometimes report "present" but not * "functional" (i.e. if disabled). Go ahead and probe them * anyway since we may enable them later. */ if (type == ACPI_TYPE_DEVICE && !acpi_DeviceIsPresent(child)) { /* Never disable PCI link devices. */ if (acpi_MatchHid(handle, "PNP0C0F")) break; /* * Docking stations should remain enabled since the system * may be undocked at boot. */ if (ACPI_SUCCESS(AcpiGetHandle(handle, "_DCK", &h))) break; device_disable(child); break; } /* * Get the device's resource settings and attach them. * Note that if the device has _PRS but no _CRS, we need * to decide when it's appropriate to try to configure the * device. Ignore the return value here; it's OK for the * device not to have any resources. */ acpi_parse_resources(child, handle, &acpi_res_parse_set, NULL); ad = device_get_ivars(child); ad->ad_cls_class = 0xffffff; if (ACPI_SUCCESS(AcpiGetObjectInfo(handle, &devinfo))) { if ((devinfo->Valid & ACPI_VALID_CLS) != 0 && devinfo->ClassCode.Length >= ACPI_PCICLS_STRING_SIZE) { ad->ad_cls_class = strtoul(devinfo->ClassCode.String, NULL, 16); } #ifdef __aarch64__ if ((devinfo->Flags & ACPI_PCI_ROOT_BRIDGE) != 0) { if (ACPI_SUCCESS(acpi_GetInteger(handle, "_SEG", &segment))) { acpi_enable_pcie(child, segment); } } #endif AcpiOsFree(devinfo); } break; } } return_ACPI_STATUS (AE_OK); } /* * AcpiAttachData() requires an object handler but never uses it. This is a * placeholder object handler so we can store a device_t in an ACPI_HANDLE. */ void acpi_fake_objhandler(ACPI_HANDLE h, void *data) { } static void acpi_shutdown_final(void *arg, int howto) { struct acpi_softc *sc = (struct acpi_softc *)arg; register_t intr; ACPI_STATUS status; /* * XXX Shutdown code should only run on the BSP (cpuid 0). * Some chipsets do not power off the system correctly if called from * an AP. */ if ((howto & RB_POWEROFF) != 0) { status = AcpiEnterSleepStatePrep(ACPI_STATE_S5); if (ACPI_FAILURE(status)) { device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", AcpiFormatException(status)); return; } device_printf(sc->acpi_dev, "Powering system off\n"); intr = intr_disable(); status = AcpiEnterSleepState(ACPI_STATE_S5); if (ACPI_FAILURE(status)) { intr_restore(intr); device_printf(sc->acpi_dev, "power-off failed - %s\n", AcpiFormatException(status)); } else { DELAY(1000000); intr_restore(intr); device_printf(sc->acpi_dev, "power-off failed - timeout\n"); } } else if ((howto & RB_HALT) == 0 && sc->acpi_handle_reboot) { /* Reboot using the reset register. */ status = AcpiReset(); if (ACPI_SUCCESS(status)) { DELAY(1000000); device_printf(sc->acpi_dev, "reset failed - timeout\n"); } else if (status != AE_NOT_EXIST) device_printf(sc->acpi_dev, "reset failed - %s\n", AcpiFormatException(status)); } else if (sc->acpi_do_disable && panicstr == NULL) { /* * Only disable ACPI if the user requested. On some systems, writing * the disable value to SMI_CMD hangs the system. */ device_printf(sc->acpi_dev, "Shutting down\n"); AcpiTerminate(); } } static void acpi_enable_fixed_events(struct acpi_softc *sc) { static int first_time = 1; /* Enable and clear fixed events and install handlers. */ if ((AcpiGbl_FADT.Flags & ACPI_FADT_POWER_BUTTON) == 0) { AcpiClearEvent(ACPI_EVENT_POWER_BUTTON); AcpiInstallFixedEventHandler(ACPI_EVENT_POWER_BUTTON, acpi_event_power_button_sleep, sc); if (first_time) device_printf(sc->acpi_dev, "Power Button (fixed)\n"); } if ((AcpiGbl_FADT.Flags & ACPI_FADT_SLEEP_BUTTON) == 0) { AcpiClearEvent(ACPI_EVENT_SLEEP_BUTTON); AcpiInstallFixedEventHandler(ACPI_EVENT_SLEEP_BUTTON, acpi_event_sleep_button_sleep, sc); if (first_time) device_printf(sc->acpi_dev, "Sleep Button (fixed)\n"); } first_time = 0; } /* * Returns true if the device is actually present and should * be attached to. This requires the present, enabled, UI-visible * and diagnostics-passed bits to be set. */ BOOLEAN acpi_DeviceIsPresent(device_t dev) { ACPI_HANDLE h; UINT32 s; ACPI_STATUS status; h = acpi_get_handle(dev); if (h == NULL) return (FALSE); status = acpi_GetInteger(h, "_STA", &s); /* * Certain Treadripper boards always returns 0 for FreeBSD because it * only returns non-zero for the OS string "Windows 2015". Otherwise it * will return zero. Force them to always be treated as present. * Beata versions were worse: they always returned 0. */ if (acpi_MatchHid(h, "AMDI0020") || acpi_MatchHid(h, "AMDI0010")) return (TRUE); /* If no _STA method, must be present */ if (ACPI_FAILURE(status)) return (status == AE_NOT_FOUND ? TRUE : FALSE); return (ACPI_DEVICE_PRESENT(s) ? TRUE : FALSE); } /* * Returns true if the battery is actually present and inserted. */ BOOLEAN acpi_BatteryIsPresent(device_t dev) { ACPI_HANDLE h; UINT32 s; ACPI_STATUS status; h = acpi_get_handle(dev); if (h == NULL) return (FALSE); status = acpi_GetInteger(h, "_STA", &s); /* If no _STA method, must be present */ if (ACPI_FAILURE(status)) return (status == AE_NOT_FOUND ? TRUE : FALSE); return (ACPI_BATTERY_PRESENT(s) ? TRUE : FALSE); } /* * Returns true if a device has at least one valid device ID. */ static BOOLEAN acpi_has_hid(ACPI_HANDLE h) { ACPI_DEVICE_INFO *devinfo; BOOLEAN ret; if (h == NULL || ACPI_FAILURE(AcpiGetObjectInfo(h, &devinfo))) return (FALSE); ret = FALSE; if ((devinfo->Valid & ACPI_VALID_HID) != 0) ret = TRUE; else if ((devinfo->Valid & ACPI_VALID_CID) != 0) if (devinfo->CompatibleIdList.Count > 0) ret = TRUE; AcpiOsFree(devinfo); return (ret); } /* * Match a HID string against a handle + * returns ACPI_MATCHHID_HID if _HID match + * ACPI_MATCHHID_CID if _CID match and not _HID match. + * ACPI_MATCHHID_NOMATCH=0 if no match. */ -BOOLEAN +int acpi_MatchHid(ACPI_HANDLE h, const char *hid) { ACPI_DEVICE_INFO *devinfo; BOOLEAN ret; int i; if (hid == NULL || h == NULL || ACPI_FAILURE(AcpiGetObjectInfo(h, &devinfo))) - return (FALSE); + return (ACPI_MATCHHID_NOMATCH); ret = FALSE; if ((devinfo->Valid & ACPI_VALID_HID) != 0 && strcmp(hid, devinfo->HardwareId.String) == 0) - ret = TRUE; + ret = ACPI_MATCHHID_HID; else if ((devinfo->Valid & ACPI_VALID_CID) != 0) for (i = 0; i < devinfo->CompatibleIdList.Count; i++) { if (strcmp(hid, devinfo->CompatibleIdList.Ids[i].String) == 0) { - ret = TRUE; + ret = ACPI_MATCHHID_CID; break; } } AcpiOsFree(devinfo); return (ret); } /* * Return the handle of a named object within our scope, ie. that of (parent) * or one if its parents. */ ACPI_STATUS acpi_GetHandleInScope(ACPI_HANDLE parent, char *path, ACPI_HANDLE *result) { ACPI_HANDLE r; ACPI_STATUS status; /* Walk back up the tree to the root */ for (;;) { status = AcpiGetHandle(parent, path, &r); if (ACPI_SUCCESS(status)) { *result = r; return (AE_OK); } /* XXX Return error here? */ if (status != AE_NOT_FOUND) return (AE_OK); if (ACPI_FAILURE(AcpiGetParent(parent, &r))) return (AE_NOT_FOUND); parent = r; } } /* * Allocate a buffer with a preset data size. */ ACPI_BUFFER * acpi_AllocBuffer(int size) { ACPI_BUFFER *buf; if ((buf = malloc(size + sizeof(*buf), M_ACPIDEV, M_NOWAIT)) == NULL) return (NULL); buf->Length = size; buf->Pointer = (void *)(buf + 1); return (buf); } ACPI_STATUS acpi_SetInteger(ACPI_HANDLE handle, char *path, UINT32 number) { ACPI_OBJECT arg1; ACPI_OBJECT_LIST args; arg1.Type = ACPI_TYPE_INTEGER; arg1.Integer.Value = number; args.Count = 1; args.Pointer = &arg1; return (AcpiEvaluateObject(handle, path, &args, NULL)); } /* * Evaluate a path that should return an integer. */ ACPI_STATUS acpi_GetInteger(ACPI_HANDLE handle, char *path, UINT32 *number) { ACPI_STATUS status; ACPI_BUFFER buf; ACPI_OBJECT param; if (handle == NULL) handle = ACPI_ROOT_OBJECT; /* * Assume that what we've been pointed at is an Integer object, or * a method that will return an Integer. */ buf.Pointer = ¶m; buf.Length = sizeof(param); status = AcpiEvaluateObject(handle, path, NULL, &buf); if (ACPI_SUCCESS(status)) { if (param.Type == ACPI_TYPE_INTEGER) *number = param.Integer.Value; else status = AE_TYPE; } /* * In some applications, a method that's expected to return an Integer * may instead return a Buffer (probably to simplify some internal * arithmetic). We'll try to fetch whatever it is, and if it's a Buffer, * convert it into an Integer as best we can. * * This is a hack. */ if (status == AE_BUFFER_OVERFLOW) { if ((buf.Pointer = AcpiOsAllocate(buf.Length)) == NULL) { status = AE_NO_MEMORY; } else { status = AcpiEvaluateObject(handle, path, NULL, &buf); if (ACPI_SUCCESS(status)) status = acpi_ConvertBufferToInteger(&buf, number); AcpiOsFree(buf.Pointer); } } return (status); } ACPI_STATUS acpi_ConvertBufferToInteger(ACPI_BUFFER *bufp, UINT32 *number) { ACPI_OBJECT *p; UINT8 *val; int i; p = (ACPI_OBJECT *)bufp->Pointer; if (p->Type == ACPI_TYPE_INTEGER) { *number = p->Integer.Value; return (AE_OK); } if (p->Type != ACPI_TYPE_BUFFER) return (AE_TYPE); if (p->Buffer.Length > sizeof(int)) return (AE_BAD_DATA); *number = 0; val = p->Buffer.Pointer; for (i = 0; i < p->Buffer.Length; i++) *number += val[i] << (i * 8); return (AE_OK); } /* * Iterate over the elements of an a package object, calling the supplied * function for each element. * * XXX possible enhancement might be to abort traversal on error. */ ACPI_STATUS acpi_ForeachPackageObject(ACPI_OBJECT *pkg, void (*func)(ACPI_OBJECT *comp, void *arg), void *arg) { ACPI_OBJECT *comp; int i; if (pkg == NULL || pkg->Type != ACPI_TYPE_PACKAGE) return (AE_BAD_PARAMETER); /* Iterate over components */ i = 0; comp = pkg->Package.Elements; for (; i < pkg->Package.Count; i++, comp++) func(comp, arg); return (AE_OK); } /* * Find the (index)th resource object in a set. */ ACPI_STATUS acpi_FindIndexedResource(ACPI_BUFFER *buf, int index, ACPI_RESOURCE **resp) { ACPI_RESOURCE *rp; int i; rp = (ACPI_RESOURCE *)buf->Pointer; i = index; while (i-- > 0) { /* Range check */ if (rp > (ACPI_RESOURCE *)((u_int8_t *)buf->Pointer + buf->Length)) return (AE_BAD_PARAMETER); /* Check for terminator */ if (rp->Type == ACPI_RESOURCE_TYPE_END_TAG || rp->Length == 0) return (AE_NOT_FOUND); rp = ACPI_NEXT_RESOURCE(rp); } if (resp != NULL) *resp = rp; return (AE_OK); } /* * Append an ACPI_RESOURCE to an ACPI_BUFFER. * * Given a pointer to an ACPI_RESOURCE structure, expand the ACPI_BUFFER * provided to contain it. If the ACPI_BUFFER is empty, allocate a sensible * backing block. If the ACPI_RESOURCE is NULL, return an empty set of * resources. */ #define ACPI_INITIAL_RESOURCE_BUFFER_SIZE 512 ACPI_STATUS acpi_AppendBufferResource(ACPI_BUFFER *buf, ACPI_RESOURCE *res) { ACPI_RESOURCE *rp; void *newp; /* Initialise the buffer if necessary. */ if (buf->Pointer == NULL) { buf->Length = ACPI_INITIAL_RESOURCE_BUFFER_SIZE; if ((buf->Pointer = AcpiOsAllocate(buf->Length)) == NULL) return (AE_NO_MEMORY); rp = (ACPI_RESOURCE *)buf->Pointer; rp->Type = ACPI_RESOURCE_TYPE_END_TAG; rp->Length = ACPI_RS_SIZE_MIN; } if (res == NULL) return (AE_OK); /* * Scan the current buffer looking for the terminator. * This will either find the terminator or hit the end * of the buffer and return an error. */ rp = (ACPI_RESOURCE *)buf->Pointer; for (;;) { /* Range check, don't go outside the buffer */ if (rp >= (ACPI_RESOURCE *)((u_int8_t *)buf->Pointer + buf->Length)) return (AE_BAD_PARAMETER); if (rp->Type == ACPI_RESOURCE_TYPE_END_TAG || rp->Length == 0) break; rp = ACPI_NEXT_RESOURCE(rp); } /* * Check the size of the buffer and expand if required. * * Required size is: * size of existing resources before terminator + * size of new resource and header + * size of terminator. * * Note that this loop should really only run once, unless * for some reason we are stuffing a *really* huge resource. */ while ((((u_int8_t *)rp - (u_int8_t *)buf->Pointer) + res->Length + ACPI_RS_SIZE_NO_DATA + ACPI_RS_SIZE_MIN) >= buf->Length) { if ((newp = AcpiOsAllocate(buf->Length * 2)) == NULL) return (AE_NO_MEMORY); bcopy(buf->Pointer, newp, buf->Length); rp = (ACPI_RESOURCE *)((u_int8_t *)newp + ((u_int8_t *)rp - (u_int8_t *)buf->Pointer)); AcpiOsFree(buf->Pointer); buf->Pointer = newp; buf->Length += buf->Length; } /* Insert the new resource. */ bcopy(res, rp, res->Length + ACPI_RS_SIZE_NO_DATA); /* And add the terminator. */ rp = ACPI_NEXT_RESOURCE(rp); rp->Type = ACPI_RESOURCE_TYPE_END_TAG; rp->Length = ACPI_RS_SIZE_MIN; return (AE_OK); } UINT8 acpi_DSMQuery(ACPI_HANDLE h, uint8_t *uuid, int revision) { /* * ACPI spec 9.1.1 defines this. * * "Arg2: Function Index Represents a specific function whose meaning is * specific to the UUID and Revision ID. Function indices should start * with 1. Function number zero is a query function (see the special * return code defined below)." */ ACPI_BUFFER buf; ACPI_OBJECT *obj; UINT8 ret = 0; if (!ACPI_SUCCESS(acpi_EvaluateDSM(h, uuid, revision, 0, NULL, &buf))) { ACPI_INFO(("Failed to enumerate DSM functions\n")); return (0); } obj = (ACPI_OBJECT *)buf.Pointer; KASSERT(obj, ("Object not allowed to be NULL\n")); /* * From ACPI 6.2 spec 9.1.1: * If Function Index = 0, a Buffer containing a function index bitfield. * Otherwise, the return value and type depends on the UUID and revision * ID (see below). */ switch (obj->Type) { case ACPI_TYPE_BUFFER: ret = *(uint8_t *)obj->Buffer.Pointer; break; case ACPI_TYPE_INTEGER: ACPI_BIOS_WARNING((AE_INFO, "Possibly buggy BIOS with ACPI_TYPE_INTEGER for function enumeration\n")); ret = obj->Integer.Value & 0xFF; break; default: ACPI_WARNING((AE_INFO, "Unexpected return type %u\n", obj->Type)); }; AcpiOsFree(obj); return ret; } /* * DSM may return multiple types depending on the function. It is therefore * unsafe to use the typed evaluation. It is highly recommended that the caller * check the type of the returned object. */ ACPI_STATUS acpi_EvaluateDSM(ACPI_HANDLE handle, uint8_t *uuid, int revision, uint64_t function, union acpi_object *package, ACPI_BUFFER *out_buf) { ACPI_OBJECT arg[4]; ACPI_OBJECT_LIST arglist; ACPI_BUFFER buf; ACPI_STATUS status; if (out_buf == NULL) return (AE_NO_MEMORY); arg[0].Type = ACPI_TYPE_BUFFER; arg[0].Buffer.Length = ACPI_UUID_LENGTH; arg[0].Buffer.Pointer = uuid; arg[1].Type = ACPI_TYPE_INTEGER; arg[1].Integer.Value = revision; arg[2].Type = ACPI_TYPE_INTEGER; arg[2].Integer.Value = function; if (package) { arg[3] = *package; } else { arg[3].Type = ACPI_TYPE_PACKAGE; arg[3].Package.Count = 0; arg[3].Package.Elements = NULL; } arglist.Pointer = arg; arglist.Count = 4; buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObject(handle, "_DSM", &arglist, &buf); if (ACPI_FAILURE(status)) return (status); KASSERT(ACPI_SUCCESS(status), ("Unexpected status")); *out_buf = buf; return (status); } ACPI_STATUS acpi_EvaluateOSC(ACPI_HANDLE handle, uint8_t *uuid, int revision, int count, uint32_t *caps_in, uint32_t *caps_out, bool query) { ACPI_OBJECT arg[4], *ret; ACPI_OBJECT_LIST arglist; ACPI_BUFFER buf; ACPI_STATUS status; arglist.Pointer = arg; arglist.Count = 4; arg[0].Type = ACPI_TYPE_BUFFER; arg[0].Buffer.Length = ACPI_UUID_LENGTH; arg[0].Buffer.Pointer = uuid; arg[1].Type = ACPI_TYPE_INTEGER; arg[1].Integer.Value = revision; arg[2].Type = ACPI_TYPE_INTEGER; arg[2].Integer.Value = count; arg[3].Type = ACPI_TYPE_BUFFER; arg[3].Buffer.Length = count * sizeof(*caps_in); arg[3].Buffer.Pointer = (uint8_t *)caps_in; caps_in[0] = query ? 1 : 0; buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObjectTyped(handle, "_OSC", &arglist, &buf, ACPI_TYPE_BUFFER); if (ACPI_FAILURE(status)) return (status); if (caps_out != NULL) { ret = buf.Pointer; if (ret->Buffer.Length != count * sizeof(*caps_out)) { AcpiOsFree(buf.Pointer); return (AE_BUFFER_OVERFLOW); } bcopy(ret->Buffer.Pointer, caps_out, ret->Buffer.Length); } AcpiOsFree(buf.Pointer); return (status); } /* * Set interrupt model. */ ACPI_STATUS acpi_SetIntrModel(int model) { return (acpi_SetInteger(ACPI_ROOT_OBJECT, "_PIC", model)); } /* * Walk subtables of a table and call a callback routine for each * subtable. The caller should provide the first subtable and a * pointer to the end of the table. This can be used to walk tables * such as MADT and SRAT that use subtable entries. */ void acpi_walk_subtables(void *first, void *end, acpi_subtable_handler *handler, void *arg) { ACPI_SUBTABLE_HEADER *entry; for (entry = first; (void *)entry < end; ) { /* Avoid an infinite loop if we hit a bogus entry. */ if (entry->Length < sizeof(ACPI_SUBTABLE_HEADER)) return; handler(entry, arg); entry = ACPI_ADD_PTR(ACPI_SUBTABLE_HEADER, entry, entry->Length); } } /* * DEPRECATED. This interface has serious deficiencies and will be * removed. * * Immediately enter the sleep state. In the old model, acpiconf(8) ran * rc.suspend and rc.resume so we don't have to notify devd(8) to do this. */ ACPI_STATUS acpi_SetSleepState(struct acpi_softc *sc, int state) { static int once; if (!once) { device_printf(sc->acpi_dev, "warning: acpi_SetSleepState() deprecated, need to update your software\n"); once = 1; } return (acpi_EnterSleepState(sc, state)); } #if defined(__amd64__) || defined(__i386__) static void acpi_sleep_force_task(void *context) { struct acpi_softc *sc = (struct acpi_softc *)context; if (ACPI_FAILURE(acpi_EnterSleepState(sc, sc->acpi_next_sstate))) device_printf(sc->acpi_dev, "force sleep state S%d failed\n", sc->acpi_next_sstate); } static void acpi_sleep_force(void *arg) { struct acpi_softc *sc = (struct acpi_softc *)arg; device_printf(sc->acpi_dev, "suspend request timed out, forcing sleep now\n"); /* * XXX Suspending from callout causes freezes in DEVICE_SUSPEND(). * Suspend from acpi_task thread instead. */ if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_sleep_force_task, sc))) device_printf(sc->acpi_dev, "AcpiOsExecute() for sleeping failed\n"); } #endif /* * Request that the system enter the given suspend state. All /dev/apm * devices and devd(8) will be notified. Userland then has a chance to * save state and acknowledge the request. The system sleeps once all * acks are in. */ int acpi_ReqSleepState(struct acpi_softc *sc, int state) { #if defined(__amd64__) || defined(__i386__) struct apm_clone_data *clone; ACPI_STATUS status; if (state < ACPI_STATE_S1 || state > ACPI_S_STATES_MAX) return (EINVAL); if (!acpi_sleep_states[state]) return (EOPNOTSUPP); /* * If a reboot/shutdown/suspend request is already in progress or * suspend is blocked due to an upcoming shutdown, just return. */ if (rebooting || sc->acpi_next_sstate != 0 || suspend_blocked) { return (0); } /* Wait until sleep is enabled. */ while (sc->acpi_sleep_disabled) { AcpiOsSleep(1000); } ACPI_LOCK(acpi); sc->acpi_next_sstate = state; /* S5 (soft-off) should be entered directly with no waiting. */ if (state == ACPI_STATE_S5) { ACPI_UNLOCK(acpi); status = acpi_EnterSleepState(sc, state); return (ACPI_SUCCESS(status) ? 0 : ENXIO); } /* Record the pending state and notify all apm devices. */ STAILQ_FOREACH(clone, &sc->apm_cdevs, entries) { clone->notify_status = APM_EV_NONE; if ((clone->flags & ACPI_EVF_DEVD) == 0) { selwakeuppri(&clone->sel_read, PZERO); KNOTE_LOCKED(&clone->sel_read.si_note, 0); } } /* If devd(8) is not running, immediately enter the sleep state. */ if (!devctl_process_running()) { ACPI_UNLOCK(acpi); status = acpi_EnterSleepState(sc, state); return (ACPI_SUCCESS(status) ? 0 : ENXIO); } /* * Set a timeout to fire if userland doesn't ack the suspend request * in time. This way we still eventually go to sleep if we were * overheating or running low on battery, even if userland is hung. * We cancel this timeout once all userland acks are in or the * suspend request is aborted. */ callout_reset(&sc->susp_force_to, 10 * hz, acpi_sleep_force, sc); ACPI_UNLOCK(acpi); /* Now notify devd(8) also. */ acpi_UserNotify("Suspend", ACPI_ROOT_OBJECT, state); return (0); #else /* This platform does not support acpi suspend/resume. */ return (EOPNOTSUPP); #endif } /* * Acknowledge (or reject) a pending sleep state. The caller has * prepared for suspend and is now ready for it to proceed. If the * error argument is non-zero, it indicates suspend should be cancelled * and gives an errno value describing why. Once all votes are in, * we suspend the system. */ int acpi_AckSleepState(struct apm_clone_data *clone, int error) { #if defined(__amd64__) || defined(__i386__) struct acpi_softc *sc; int ret, sleeping; /* If no pending sleep state, return an error. */ ACPI_LOCK(acpi); sc = clone->acpi_sc; if (sc->acpi_next_sstate == 0) { ACPI_UNLOCK(acpi); return (ENXIO); } /* Caller wants to abort suspend process. */ if (error) { sc->acpi_next_sstate = 0; callout_stop(&sc->susp_force_to); device_printf(sc->acpi_dev, "listener on %s cancelled the pending suspend\n", devtoname(clone->cdev)); ACPI_UNLOCK(acpi); return (0); } /* * Mark this device as acking the suspend request. Then, walk through * all devices, seeing if they agree yet. We only count devices that * are writable since read-only devices couldn't ack the request. */ sleeping = TRUE; clone->notify_status = APM_EV_ACKED; STAILQ_FOREACH(clone, &sc->apm_cdevs, entries) { if ((clone->flags & ACPI_EVF_WRITE) != 0 && clone->notify_status != APM_EV_ACKED) { sleeping = FALSE; break; } } /* If all devices have voted "yes", we will suspend now. */ if (sleeping) callout_stop(&sc->susp_force_to); ACPI_UNLOCK(acpi); ret = 0; if (sleeping) { if (ACPI_FAILURE(acpi_EnterSleepState(sc, sc->acpi_next_sstate))) ret = ENODEV; } return (ret); #else /* This platform does not support acpi suspend/resume. */ return (EOPNOTSUPP); #endif } static void acpi_sleep_enable(void *arg) { struct acpi_softc *sc = (struct acpi_softc *)arg; ACPI_LOCK_ASSERT(acpi); /* Reschedule if the system is not fully up and running. */ if (!AcpiGbl_SystemAwakeAndRunning) { callout_schedule(&acpi_sleep_timer, hz * ACPI_MINIMUM_AWAKETIME); return; } sc->acpi_sleep_disabled = FALSE; } static ACPI_STATUS acpi_sleep_disable(struct acpi_softc *sc) { ACPI_STATUS status; /* Fail if the system is not fully up and running. */ if (!AcpiGbl_SystemAwakeAndRunning) return (AE_ERROR); ACPI_LOCK(acpi); status = sc->acpi_sleep_disabled ? AE_ERROR : AE_OK; sc->acpi_sleep_disabled = TRUE; ACPI_UNLOCK(acpi); return (status); } enum acpi_sleep_state { ACPI_SS_NONE, ACPI_SS_GPE_SET, ACPI_SS_DEV_SUSPEND, ACPI_SS_SLP_PREP, ACPI_SS_SLEPT, }; /* * Enter the desired system sleep state. * * Currently we support S1-S5 but S4 is only S4BIOS */ static ACPI_STATUS acpi_EnterSleepState(struct acpi_softc *sc, int state) { register_t intr; ACPI_STATUS status; ACPI_EVENT_STATUS power_button_status; enum acpi_sleep_state slp_state; int sleep_result; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state); if (state < ACPI_STATE_S1 || state > ACPI_S_STATES_MAX) return_ACPI_STATUS (AE_BAD_PARAMETER); if (!acpi_sleep_states[state]) { device_printf(sc->acpi_dev, "Sleep state S%d not supported by BIOS\n", state); return (AE_SUPPORT); } /* Re-entry once we're suspending is not allowed. */ status = acpi_sleep_disable(sc); if (ACPI_FAILURE(status)) { device_printf(sc->acpi_dev, "suspend request ignored (not ready yet)\n"); return (status); } if (state == ACPI_STATE_S5) { /* * Shut down cleanly and power off. This will call us back through the * shutdown handlers. */ shutdown_nice(RB_POWEROFF); return_ACPI_STATUS (AE_OK); } EVENTHANDLER_INVOKE(power_suspend_early); stop_all_proc(); EVENTHANDLER_INVOKE(power_suspend); #ifdef EARLY_AP_STARTUP MPASS(mp_ncpus == 1 || smp_started); thread_lock(curthread); sched_bind(curthread, 0); thread_unlock(curthread); #else if (smp_started) { thread_lock(curthread); sched_bind(curthread, 0); thread_unlock(curthread); } #endif /* * Be sure to hold Giant across DEVICE_SUSPEND/RESUME since non-MPSAFE * drivers need this. */ mtx_lock(&Giant); slp_state = ACPI_SS_NONE; sc->acpi_sstate = state; /* Enable any GPEs as appropriate and requested by the user. */ acpi_wake_prep_walk(state); slp_state = ACPI_SS_GPE_SET; /* * Inform all devices that we are going to sleep. If at least one * device fails, DEVICE_SUSPEND() automatically resumes the tree. * * XXX Note that a better two-pass approach with a 'veto' pass * followed by a "real thing" pass would be better, but the current * bus interface does not provide for this. */ if (DEVICE_SUSPEND(root_bus) != 0) { device_printf(sc->acpi_dev, "device_suspend failed\n"); goto backout; } slp_state = ACPI_SS_DEV_SUSPEND; status = AcpiEnterSleepStatePrep(state); if (ACPI_FAILURE(status)) { device_printf(sc->acpi_dev, "AcpiEnterSleepStatePrep failed - %s\n", AcpiFormatException(status)); goto backout; } slp_state = ACPI_SS_SLP_PREP; if (sc->acpi_sleep_delay > 0) DELAY(sc->acpi_sleep_delay * 1000000); suspendclock(); intr = intr_disable(); if (state != ACPI_STATE_S1) { sleep_result = acpi_sleep_machdep(sc, state); acpi_wakeup_machdep(sc, state, sleep_result, 0); /* * XXX According to ACPI specification SCI_EN bit should be restored * by ACPI platform (BIOS, firmware) to its pre-sleep state. * Unfortunately some BIOSes fail to do that and that leads to * unexpected and serious consequences during wake up like a system * getting stuck in SMI handlers. * This hack is picked up from Linux, which claims that it follows * Windows behavior. */ if (sleep_result == 1 && state != ACPI_STATE_S4) AcpiWriteBitRegister(ACPI_BITREG_SCI_ENABLE, ACPI_ENABLE_EVENT); if (sleep_result == 1 && state == ACPI_STATE_S3) { /* * Prevent mis-interpretation of the wakeup by power button * as a request for power off. * Ideally we should post an appropriate wakeup event, * perhaps using acpi_event_power_button_wake or alike. * * Clearing of power button status after wakeup is mandated * by ACPI specification in section "Fixed Power Button". * * XXX As of ACPICA 20121114 AcpiGetEventStatus provides * status as 0/1 corressponding to inactive/active despite * its type being ACPI_EVENT_STATUS. In other words, * we should not test for ACPI_EVENT_FLAG_SET for time being. */ if (ACPI_SUCCESS(AcpiGetEventStatus(ACPI_EVENT_POWER_BUTTON, &power_button_status)) && power_button_status != 0) { AcpiClearEvent(ACPI_EVENT_POWER_BUTTON); device_printf(sc->acpi_dev, "cleared fixed power button status\n"); } } intr_restore(intr); /* call acpi_wakeup_machdep() again with interrupt enabled */ acpi_wakeup_machdep(sc, state, sleep_result, 1); AcpiLeaveSleepStatePrep(state); if (sleep_result == -1) goto backout; /* Re-enable ACPI hardware on wakeup from sleep state 4. */ if (state == ACPI_STATE_S4) AcpiEnable(); } else { status = AcpiEnterSleepState(state); intr_restore(intr); AcpiLeaveSleepStatePrep(state); if (ACPI_FAILURE(status)) { device_printf(sc->acpi_dev, "AcpiEnterSleepState failed - %s\n", AcpiFormatException(status)); goto backout; } } slp_state = ACPI_SS_SLEPT; /* * Back out state according to how far along we got in the suspend * process. This handles both the error and success cases. */ backout: if (slp_state >= ACPI_SS_SLP_PREP) resumeclock(); if (slp_state >= ACPI_SS_GPE_SET) { acpi_wake_prep_walk(state); sc->acpi_sstate = ACPI_STATE_S0; } if (slp_state >= ACPI_SS_DEV_SUSPEND) DEVICE_RESUME(root_bus); if (slp_state >= ACPI_SS_SLP_PREP) AcpiLeaveSleepState(state); if (slp_state >= ACPI_SS_SLEPT) { #if defined(__i386__) || defined(__amd64__) /* NB: we are still using ACPI timecounter at this point. */ resume_TSC(); #endif acpi_resync_clock(sc); acpi_enable_fixed_events(sc); } sc->acpi_next_sstate = 0; mtx_unlock(&Giant); #ifdef EARLY_AP_STARTUP thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); #else if (smp_started) { thread_lock(curthread); sched_unbind(curthread); thread_unlock(curthread); } #endif resume_all_proc(); EVENTHANDLER_INVOKE(power_resume); /* Allow another sleep request after a while. */ callout_schedule(&acpi_sleep_timer, hz * ACPI_MINIMUM_AWAKETIME); /* Run /etc/rc.resume after we are back. */ if (devctl_process_running()) acpi_UserNotify("Resume", ACPI_ROOT_OBJECT, state); return_ACPI_STATUS (status); } static void acpi_resync_clock(struct acpi_softc *sc) { /* * Warm up timecounter again and reset system clock. */ (void)timecounter->tc_get_timecount(timecounter); (void)timecounter->tc_get_timecount(timecounter); inittodr(time_second + sc->acpi_sleep_delay); } /* Enable or disable the device's wake GPE. */ int acpi_wake_set_enable(device_t dev, int enable) { struct acpi_prw_data prw; ACPI_STATUS status; int flags; /* Make sure the device supports waking the system and get the GPE. */ if (acpi_parse_prw(acpi_get_handle(dev), &prw) != 0) return (ENXIO); flags = acpi_get_flags(dev); if (enable) { status = AcpiSetGpeWakeMask(prw.gpe_handle, prw.gpe_bit, ACPI_GPE_ENABLE); if (ACPI_FAILURE(status)) { device_printf(dev, "enable wake failed\n"); return (ENXIO); } acpi_set_flags(dev, flags | ACPI_FLAG_WAKE_ENABLED); } else { status = AcpiSetGpeWakeMask(prw.gpe_handle, prw.gpe_bit, ACPI_GPE_DISABLE); if (ACPI_FAILURE(status)) { device_printf(dev, "disable wake failed\n"); return (ENXIO); } acpi_set_flags(dev, flags & ~ACPI_FLAG_WAKE_ENABLED); } return (0); } static int acpi_wake_sleep_prep(ACPI_HANDLE handle, int sstate) { struct acpi_prw_data prw; device_t dev; /* Check that this is a wake-capable device and get its GPE. */ if (acpi_parse_prw(handle, &prw) != 0) return (ENXIO); dev = acpi_get_device(handle); /* * The destination sleep state must be less than (i.e., higher power) * or equal to the value specified by _PRW. If this GPE cannot be * enabled for the next sleep state, then disable it. If it can and * the user requested it be enabled, turn on any required power resources * and set _PSW. */ if (sstate > prw.lowest_wake) { AcpiSetGpeWakeMask(prw.gpe_handle, prw.gpe_bit, ACPI_GPE_DISABLE); if (bootverbose) device_printf(dev, "wake_prep disabled wake for %s (S%d)\n", acpi_name(handle), sstate); } else if (dev && (acpi_get_flags(dev) & ACPI_FLAG_WAKE_ENABLED) != 0) { acpi_pwr_wake_enable(handle, 1); acpi_SetInteger(handle, "_PSW", 1); if (bootverbose) device_printf(dev, "wake_prep enabled for %s (S%d)\n", acpi_name(handle), sstate); } return (0); } static int acpi_wake_run_prep(ACPI_HANDLE handle, int sstate) { struct acpi_prw_data prw; device_t dev; /* * Check that this is a wake-capable device and get its GPE. Return * now if the user didn't enable this device for wake. */ if (acpi_parse_prw(handle, &prw) != 0) return (ENXIO); dev = acpi_get_device(handle); if (dev == NULL || (acpi_get_flags(dev) & ACPI_FLAG_WAKE_ENABLED) == 0) return (0); /* * If this GPE couldn't be enabled for the previous sleep state, it was * disabled before going to sleep so re-enable it. If it was enabled, * clear _PSW and turn off any power resources it used. */ if (sstate > prw.lowest_wake) { AcpiSetGpeWakeMask(prw.gpe_handle, prw.gpe_bit, ACPI_GPE_ENABLE); if (bootverbose) device_printf(dev, "run_prep re-enabled %s\n", acpi_name(handle)); } else { acpi_SetInteger(handle, "_PSW", 0); acpi_pwr_wake_enable(handle, 0); if (bootverbose) device_printf(dev, "run_prep cleaned up for %s\n", acpi_name(handle)); } return (0); } static ACPI_STATUS acpi_wake_prep(ACPI_HANDLE handle, UINT32 level, void *context, void **status) { int sstate; /* If suspending, run the sleep prep function, otherwise wake. */ sstate = *(int *)context; if (AcpiGbl_SystemAwakeAndRunning) acpi_wake_sleep_prep(handle, sstate); else acpi_wake_run_prep(handle, sstate); return (AE_OK); } /* Walk the tree rooted at acpi0 to prep devices for suspend/resume. */ static int acpi_wake_prep_walk(int sstate) { ACPI_HANDLE sb_handle; if (ACPI_SUCCESS(AcpiGetHandle(ACPI_ROOT_OBJECT, "\\_SB_", &sb_handle))) AcpiWalkNamespace(ACPI_TYPE_DEVICE, sb_handle, 100, acpi_wake_prep, NULL, &sstate, NULL); return (0); } /* Walk the tree rooted at acpi0 to attach per-device wake sysctls. */ static int acpi_wake_sysctl_walk(device_t dev) { int error, i, numdevs; device_t *devlist; device_t child; ACPI_STATUS status; error = device_get_children(dev, &devlist, &numdevs); if (error != 0 || numdevs == 0) { if (numdevs == 0) free(devlist, M_TEMP); return (error); } for (i = 0; i < numdevs; i++) { child = devlist[i]; acpi_wake_sysctl_walk(child); if (!device_is_attached(child)) continue; status = AcpiEvaluateObject(acpi_get_handle(child), "_PRW", NULL, NULL); if (ACPI_SUCCESS(status)) { SYSCTL_ADD_PROC(device_get_sysctl_ctx(child), SYSCTL_CHILDREN(device_get_sysctl_tree(child)), OID_AUTO, "wake", CTLTYPE_INT | CTLFLAG_RW, child, 0, acpi_wake_set_sysctl, "I", "Device set to wake the system"); } } free(devlist, M_TEMP); return (0); } /* Enable or disable wake from userland. */ static int acpi_wake_set_sysctl(SYSCTL_HANDLER_ARGS) { int enable, error; device_t dev; dev = (device_t)arg1; enable = (acpi_get_flags(dev) & ACPI_FLAG_WAKE_ENABLED) ? 1 : 0; error = sysctl_handle_int(oidp, &enable, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (enable != 0 && enable != 1) return (EINVAL); return (acpi_wake_set_enable(dev, enable)); } /* Parse a device's _PRW into a structure. */ int acpi_parse_prw(ACPI_HANDLE h, struct acpi_prw_data *prw) { ACPI_STATUS status; ACPI_BUFFER prw_buffer; ACPI_OBJECT *res, *res2; int error, i, power_count; if (h == NULL || prw == NULL) return (EINVAL); /* * The _PRW object (7.2.9) is only required for devices that have the * ability to wake the system from a sleeping state. */ error = EINVAL; prw_buffer.Pointer = NULL; prw_buffer.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObject(h, "_PRW", NULL, &prw_buffer); if (ACPI_FAILURE(status)) return (ENOENT); res = (ACPI_OBJECT *)prw_buffer.Pointer; if (res == NULL) return (ENOENT); if (!ACPI_PKG_VALID(res, 2)) goto out; /* * Element 1 of the _PRW object: * The lowest power system sleeping state that can be entered while still * providing wake functionality. The sleeping state being entered must * be less than (i.e., higher power) or equal to this value. */ if (acpi_PkgInt32(res, 1, &prw->lowest_wake) != 0) goto out; /* * Element 0 of the _PRW object: */ switch (res->Package.Elements[0].Type) { case ACPI_TYPE_INTEGER: /* * If the data type of this package element is numeric, then this * _PRW package element is the bit index in the GPEx_EN, in the * GPE blocks described in the FADT, of the enable bit that is * enabled for the wake event. */ prw->gpe_handle = NULL; prw->gpe_bit = res->Package.Elements[0].Integer.Value; error = 0; break; case ACPI_TYPE_PACKAGE: /* * If the data type of this package element is a package, then this * _PRW package element is itself a package containing two * elements. The first is an object reference to the GPE Block * device that contains the GPE that will be triggered by the wake * event. The second element is numeric and it contains the bit * index in the GPEx_EN, in the GPE Block referenced by the * first element in the package, of the enable bit that is enabled for * the wake event. * * For example, if this field is a package then it is of the form: * Package() {\_SB.PCI0.ISA.GPE, 2} */ res2 = &res->Package.Elements[0]; if (!ACPI_PKG_VALID(res2, 2)) goto out; prw->gpe_handle = acpi_GetReference(NULL, &res2->Package.Elements[0]); if (prw->gpe_handle == NULL) goto out; if (acpi_PkgInt32(res2, 1, &prw->gpe_bit) != 0) goto out; error = 0; break; default: goto out; } /* Elements 2 to N of the _PRW object are power resources. */ power_count = res->Package.Count - 2; if (power_count > ACPI_PRW_MAX_POWERRES) { printf("ACPI device %s has too many power resources\n", acpi_name(h)); power_count = 0; } prw->power_res_count = power_count; for (i = 0; i < power_count; i++) prw->power_res[i] = res->Package.Elements[i]; out: if (prw_buffer.Pointer != NULL) AcpiOsFree(prw_buffer.Pointer); return (error); } /* * ACPI Event Handlers */ /* System Event Handlers (registered by EVENTHANDLER_REGISTER) */ static void acpi_system_eventhandler_sleep(void *arg, int state) { struct acpi_softc *sc = (struct acpi_softc *)arg; int ret; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state); /* Check if button action is disabled or unknown. */ if (state == ACPI_STATE_UNKNOWN) return; /* Request that the system prepare to enter the given suspend state. */ ret = acpi_ReqSleepState(sc, state); if (ret != 0) device_printf(sc->acpi_dev, "request to enter state S%d failed (err %d)\n", state, ret); return_VOID; } static void acpi_system_eventhandler_wakeup(void *arg, int state) { ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, state); /* Currently, nothing to do for wakeup. */ return_VOID; } /* * ACPICA Event Handlers (FixedEvent, also called from button notify handler) */ static void acpi_invoke_sleep_eventhandler(void *context) { EVENTHANDLER_INVOKE(acpi_sleep_event, *(int *)context); } static void acpi_invoke_wake_eventhandler(void *context) { EVENTHANDLER_INVOKE(acpi_wakeup_event, *(int *)context); } UINT32 acpi_event_power_button_sleep(void *context) { struct acpi_softc *sc = (struct acpi_softc *)context; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_invoke_sleep_eventhandler, &sc->acpi_power_button_sx))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); return_VALUE (ACPI_INTERRUPT_HANDLED); } UINT32 acpi_event_power_button_wake(void *context) { struct acpi_softc *sc = (struct acpi_softc *)context; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_invoke_wake_eventhandler, &sc->acpi_power_button_sx))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); return_VALUE (ACPI_INTERRUPT_HANDLED); } UINT32 acpi_event_sleep_button_sleep(void *context) { struct acpi_softc *sc = (struct acpi_softc *)context; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_invoke_sleep_eventhandler, &sc->acpi_sleep_button_sx))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); return_VALUE (ACPI_INTERRUPT_HANDLED); } UINT32 acpi_event_sleep_button_wake(void *context) { struct acpi_softc *sc = (struct acpi_softc *)context; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); if (ACPI_FAILURE(AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_invoke_wake_eventhandler, &sc->acpi_sleep_button_sx))) return_VALUE (ACPI_INTERRUPT_NOT_HANDLED); return_VALUE (ACPI_INTERRUPT_HANDLED); } /* * XXX This static buffer is suboptimal. There is no locking so only * use this for single-threaded callers. */ char * acpi_name(ACPI_HANDLE handle) { ACPI_BUFFER buf; static char data[256]; buf.Length = sizeof(data); buf.Pointer = data; if (handle && ACPI_SUCCESS(AcpiGetName(handle, ACPI_FULL_PATHNAME, &buf))) return (data); return ("(unknown)"); } /* * Debugging/bug-avoidance. Avoid trying to fetch info on various * parts of the namespace. */ int acpi_avoid(ACPI_HANDLE handle) { char *cp, *env, *np; int len; np = acpi_name(handle); if (*np == '\\') np++; if ((env = kern_getenv("debug.acpi.avoid")) == NULL) return (0); /* Scan the avoid list checking for a match */ cp = env; for (;;) { while (*cp != 0 && isspace(*cp)) cp++; if (*cp == 0) break; len = 0; while (cp[len] != 0 && !isspace(cp[len])) len++; if (!strncmp(cp, np, len)) { freeenv(env); return(1); } cp += len; } freeenv(env); return (0); } /* * Debugging/bug-avoidance. Disable ACPI subsystem components. */ int acpi_disabled(char *subsys) { char *cp, *env; int len; if ((env = kern_getenv("debug.acpi.disabled")) == NULL) return (0); if (strcmp(env, "all") == 0) { freeenv(env); return (1); } /* Scan the disable list, checking for a match. */ cp = env; for (;;) { while (*cp != '\0' && isspace(*cp)) cp++; if (*cp == '\0') break; len = 0; while (cp[len] != '\0' && !isspace(cp[len])) len++; if (strncmp(cp, subsys, len) == 0) { freeenv(env); return (1); } cp += len; } freeenv(env); return (0); } static void acpi_lookup(void *arg, const char *name, device_t *dev) { ACPI_HANDLE handle; if (*dev != NULL) return; /* * Allow any handle name that is specified as an absolute path and * starts with '\'. We could restrict this to \_SB and friends, * but see acpi_probe_children() for notes on why we scan the entire * namespace for devices. * * XXX: The pathname argument to AcpiGetHandle() should be fixed to * be const. */ if (name[0] != '\\') return; if (ACPI_FAILURE(AcpiGetHandle(ACPI_ROOT_OBJECT, __DECONST(char *, name), &handle))) return; *dev = acpi_get_device(handle); } /* * Control interface. * * We multiplex ioctls for all participating ACPI devices here. Individual * drivers wanting to be accessible via /dev/acpi should use the * register/deregister interface to make their handlers visible. */ struct acpi_ioctl_hook { TAILQ_ENTRY(acpi_ioctl_hook) link; u_long cmd; acpi_ioctl_fn fn; void *arg; }; static TAILQ_HEAD(,acpi_ioctl_hook) acpi_ioctl_hooks; static int acpi_ioctl_hooks_initted; int acpi_register_ioctl(u_long cmd, acpi_ioctl_fn fn, void *arg) { struct acpi_ioctl_hook *hp; if ((hp = malloc(sizeof(*hp), M_ACPIDEV, M_NOWAIT)) == NULL) return (ENOMEM); hp->cmd = cmd; hp->fn = fn; hp->arg = arg; ACPI_LOCK(acpi); if (acpi_ioctl_hooks_initted == 0) { TAILQ_INIT(&acpi_ioctl_hooks); acpi_ioctl_hooks_initted = 1; } TAILQ_INSERT_TAIL(&acpi_ioctl_hooks, hp, link); ACPI_UNLOCK(acpi); return (0); } void acpi_deregister_ioctl(u_long cmd, acpi_ioctl_fn fn) { struct acpi_ioctl_hook *hp; ACPI_LOCK(acpi); TAILQ_FOREACH(hp, &acpi_ioctl_hooks, link) if (hp->cmd == cmd && hp->fn == fn) break; if (hp != NULL) { TAILQ_REMOVE(&acpi_ioctl_hooks, hp, link); free(hp, M_ACPIDEV); } ACPI_UNLOCK(acpi); } static int acpiopen(struct cdev *dev, int flag, int fmt, struct thread *td) { return (0); } static int acpiclose(struct cdev *dev, int flag, int fmt, struct thread *td) { return (0); } static int acpiioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { struct acpi_softc *sc; struct acpi_ioctl_hook *hp; int error, state; error = 0; hp = NULL; sc = dev->si_drv1; /* * Scan the list of registered ioctls, looking for handlers. */ ACPI_LOCK(acpi); if (acpi_ioctl_hooks_initted) TAILQ_FOREACH(hp, &acpi_ioctl_hooks, link) { if (hp->cmd == cmd) break; } ACPI_UNLOCK(acpi); if (hp) return (hp->fn(cmd, addr, hp->arg)); /* * Core ioctls are not permitted for non-writable user. * Currently, other ioctls just fetch information. * Not changing system behavior. */ if ((flag & FWRITE) == 0) return (EPERM); /* Core system ioctls. */ switch (cmd) { case ACPIIO_REQSLPSTATE: state = *(int *)addr; if (state != ACPI_STATE_S5) return (acpi_ReqSleepState(sc, state)); device_printf(sc->acpi_dev, "power off via acpi ioctl not supported\n"); error = EOPNOTSUPP; break; case ACPIIO_ACKSLPSTATE: error = *(int *)addr; error = acpi_AckSleepState(sc->acpi_clone, error); break; case ACPIIO_SETSLPSTATE: /* DEPRECATED */ state = *(int *)addr; if (state < ACPI_STATE_S0 || state > ACPI_S_STATES_MAX) return (EINVAL); if (!acpi_sleep_states[state]) return (EOPNOTSUPP); if (ACPI_FAILURE(acpi_SetSleepState(sc, state))) error = ENXIO; break; default: error = ENXIO; break; } return (error); } static int acpi_sname2sstate(const char *sname) { int sstate; if (toupper(sname[0]) == 'S') { sstate = sname[1] - '0'; if (sstate >= ACPI_STATE_S0 && sstate <= ACPI_STATE_S5 && sname[2] == '\0') return (sstate); } else if (strcasecmp(sname, "NONE") == 0) return (ACPI_STATE_UNKNOWN); return (-1); } static const char * acpi_sstate2sname(int sstate) { static const char *snames[] = { "S0", "S1", "S2", "S3", "S4", "S5" }; if (sstate >= ACPI_STATE_S0 && sstate <= ACPI_STATE_S5) return (snames[sstate]); else if (sstate == ACPI_STATE_UNKNOWN) return ("NONE"); return (NULL); } static int acpi_supported_sleep_state_sysctl(SYSCTL_HANDLER_ARGS) { int error; struct sbuf sb; UINT8 state; sbuf_new(&sb, NULL, 32, SBUF_AUTOEXTEND); for (state = ACPI_STATE_S1; state < ACPI_S_STATE_COUNT; state++) if (acpi_sleep_states[state]) sbuf_printf(&sb, "%s ", acpi_sstate2sname(state)); sbuf_trim(&sb); sbuf_finish(&sb); error = sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); sbuf_delete(&sb); return (error); } static int acpi_sleep_state_sysctl(SYSCTL_HANDLER_ARGS) { char sleep_state[10]; int error, new_state, old_state; old_state = *(int *)oidp->oid_arg1; strlcpy(sleep_state, acpi_sstate2sname(old_state), sizeof(sleep_state)); error = sysctl_handle_string(oidp, sleep_state, sizeof(sleep_state), req); if (error == 0 && req->newptr != NULL) { new_state = acpi_sname2sstate(sleep_state); if (new_state < ACPI_STATE_S1) return (EINVAL); if (new_state < ACPI_S_STATE_COUNT && !acpi_sleep_states[new_state]) return (EOPNOTSUPP); if (new_state != old_state) *(int *)oidp->oid_arg1 = new_state; } return (error); } /* Inform devctl(4) when we receive a Notify. */ void acpi_UserNotify(const char *subsystem, ACPI_HANDLE h, uint8_t notify) { char notify_buf[16]; ACPI_BUFFER handle_buf; ACPI_STATUS status; if (subsystem == NULL) return; handle_buf.Pointer = NULL; handle_buf.Length = ACPI_ALLOCATE_BUFFER; status = AcpiNsHandleToPathname(h, &handle_buf, FALSE); if (ACPI_FAILURE(status)) return; snprintf(notify_buf, sizeof(notify_buf), "notify=0x%02x", notify); devctl_notify("ACPI", subsystem, handle_buf.Pointer, notify_buf); AcpiOsFree(handle_buf.Pointer); } #ifdef ACPI_DEBUG /* * Support for parsing debug options from the kernel environment. * * Bits may be set in the AcpiDbgLayer and AcpiDbgLevel debug registers * by specifying the names of the bits in the debug.acpi.layer and * debug.acpi.level environment variables. Bits may be unset by * prefixing the bit name with !. */ struct debugtag { char *name; UINT32 value; }; static struct debugtag dbg_layer[] = { {"ACPI_UTILITIES", ACPI_UTILITIES}, {"ACPI_HARDWARE", ACPI_HARDWARE}, {"ACPI_EVENTS", ACPI_EVENTS}, {"ACPI_TABLES", ACPI_TABLES}, {"ACPI_NAMESPACE", ACPI_NAMESPACE}, {"ACPI_PARSER", ACPI_PARSER}, {"ACPI_DISPATCHER", ACPI_DISPATCHER}, {"ACPI_EXECUTER", ACPI_EXECUTER}, {"ACPI_RESOURCES", ACPI_RESOURCES}, {"ACPI_CA_DEBUGGER", ACPI_CA_DEBUGGER}, {"ACPI_OS_SERVICES", ACPI_OS_SERVICES}, {"ACPI_CA_DISASSEMBLER", ACPI_CA_DISASSEMBLER}, {"ACPI_ALL_COMPONENTS", ACPI_ALL_COMPONENTS}, {"ACPI_AC_ADAPTER", ACPI_AC_ADAPTER}, {"ACPI_BATTERY", ACPI_BATTERY}, {"ACPI_BUS", ACPI_BUS}, {"ACPI_BUTTON", ACPI_BUTTON}, {"ACPI_EC", ACPI_EC}, {"ACPI_FAN", ACPI_FAN}, {"ACPI_POWERRES", ACPI_POWERRES}, {"ACPI_PROCESSOR", ACPI_PROCESSOR}, {"ACPI_THERMAL", ACPI_THERMAL}, {"ACPI_TIMER", ACPI_TIMER}, {"ACPI_ALL_DRIVERS", ACPI_ALL_DRIVERS}, {NULL, 0} }; static struct debugtag dbg_level[] = { {"ACPI_LV_INIT", ACPI_LV_INIT}, {"ACPI_LV_DEBUG_OBJECT", ACPI_LV_DEBUG_OBJECT}, {"ACPI_LV_INFO", ACPI_LV_INFO}, {"ACPI_LV_REPAIR", ACPI_LV_REPAIR}, {"ACPI_LV_ALL_EXCEPTIONS", ACPI_LV_ALL_EXCEPTIONS}, /* Trace verbosity level 1 [Standard Trace Level] */ {"ACPI_LV_INIT_NAMES", ACPI_LV_INIT_NAMES}, {"ACPI_LV_PARSE", ACPI_LV_PARSE}, {"ACPI_LV_LOAD", ACPI_LV_LOAD}, {"ACPI_LV_DISPATCH", ACPI_LV_DISPATCH}, {"ACPI_LV_EXEC", ACPI_LV_EXEC}, {"ACPI_LV_NAMES", ACPI_LV_NAMES}, {"ACPI_LV_OPREGION", ACPI_LV_OPREGION}, {"ACPI_LV_BFIELD", ACPI_LV_BFIELD}, {"ACPI_LV_TABLES", ACPI_LV_TABLES}, {"ACPI_LV_VALUES", ACPI_LV_VALUES}, {"ACPI_LV_OBJECTS", ACPI_LV_OBJECTS}, {"ACPI_LV_RESOURCES", ACPI_LV_RESOURCES}, {"ACPI_LV_USER_REQUESTS", ACPI_LV_USER_REQUESTS}, {"ACPI_LV_PACKAGE", ACPI_LV_PACKAGE}, {"ACPI_LV_VERBOSITY1", ACPI_LV_VERBOSITY1}, /* Trace verbosity level 2 [Function tracing and memory allocation] */ {"ACPI_LV_ALLOCATIONS", ACPI_LV_ALLOCATIONS}, {"ACPI_LV_FUNCTIONS", ACPI_LV_FUNCTIONS}, {"ACPI_LV_OPTIMIZATIONS", ACPI_LV_OPTIMIZATIONS}, {"ACPI_LV_VERBOSITY2", ACPI_LV_VERBOSITY2}, {"ACPI_LV_ALL", ACPI_LV_ALL}, /* Trace verbosity level 3 [Threading, I/O, and Interrupts] */ {"ACPI_LV_MUTEX", ACPI_LV_MUTEX}, {"ACPI_LV_THREADS", ACPI_LV_THREADS}, {"ACPI_LV_IO", ACPI_LV_IO}, {"ACPI_LV_INTERRUPTS", ACPI_LV_INTERRUPTS}, {"ACPI_LV_VERBOSITY3", ACPI_LV_VERBOSITY3}, /* Exceptionally verbose output -- also used in the global "DebugLevel" */ {"ACPI_LV_AML_DISASSEMBLE", ACPI_LV_AML_DISASSEMBLE}, {"ACPI_LV_VERBOSE_INFO", ACPI_LV_VERBOSE_INFO}, {"ACPI_LV_FULL_TABLES", ACPI_LV_FULL_TABLES}, {"ACPI_LV_EVENTS", ACPI_LV_EVENTS}, {"ACPI_LV_VERBOSE", ACPI_LV_VERBOSE}, {NULL, 0} }; static void acpi_parse_debug(char *cp, struct debugtag *tag, UINT32 *flag) { char *ep; int i, l; int set; while (*cp) { if (isspace(*cp)) { cp++; continue; } ep = cp; while (*ep && !isspace(*ep)) ep++; if (*cp == '!') { set = 0; cp++; if (cp == ep) continue; } else { set = 1; } l = ep - cp; for (i = 0; tag[i].name != NULL; i++) { if (!strncmp(cp, tag[i].name, l)) { if (set) *flag |= tag[i].value; else *flag &= ~tag[i].value; } } cp = ep; } } static void acpi_set_debugging(void *junk) { char *layer, *level; if (cold) { AcpiDbgLayer = 0; AcpiDbgLevel = 0; } layer = kern_getenv("debug.acpi.layer"); level = kern_getenv("debug.acpi.level"); if (layer == NULL && level == NULL) return; printf("ACPI set debug"); if (layer != NULL) { if (strcmp("NONE", layer) != 0) printf(" layer '%s'", layer); acpi_parse_debug(layer, &dbg_layer[0], &AcpiDbgLayer); freeenv(layer); } if (level != NULL) { if (strcmp("NONE", level) != 0) printf(" level '%s'", level); acpi_parse_debug(level, &dbg_level[0], &AcpiDbgLevel); freeenv(level); } printf("\n"); } SYSINIT(acpi_debugging, SI_SUB_TUNABLES, SI_ORDER_ANY, acpi_set_debugging, NULL); static int acpi_debug_sysctl(SYSCTL_HANDLER_ARGS) { int error, *dbg; struct debugtag *tag; struct sbuf sb; char temp[128]; if (sbuf_new(&sb, NULL, 128, SBUF_AUTOEXTEND) == NULL) return (ENOMEM); if (strcmp(oidp->oid_arg1, "debug.acpi.layer") == 0) { tag = &dbg_layer[0]; dbg = &AcpiDbgLayer; } else { tag = &dbg_level[0]; dbg = &AcpiDbgLevel; } /* Get old values if this is a get request. */ ACPI_SERIAL_BEGIN(acpi); if (*dbg == 0) { sbuf_cpy(&sb, "NONE"); } else if (req->newptr == NULL) { for (; tag->name != NULL; tag++) { if ((*dbg & tag->value) == tag->value) sbuf_printf(&sb, "%s ", tag->name); } } sbuf_trim(&sb); sbuf_finish(&sb); strlcpy(temp, sbuf_data(&sb), sizeof(temp)); sbuf_delete(&sb); error = sysctl_handle_string(oidp, temp, sizeof(temp), req); /* Check for error or no change */ if (error == 0 && req->newptr != NULL) { *dbg = 0; kern_setenv((char *)oidp->oid_arg1, temp); acpi_set_debugging(NULL); } ACPI_SERIAL_END(acpi); return (error); } SYSCTL_PROC(_debug_acpi, OID_AUTO, layer, CTLFLAG_RW | CTLTYPE_STRING, "debug.acpi.layer", 0, acpi_debug_sysctl, "A", ""); SYSCTL_PROC(_debug_acpi, OID_AUTO, level, CTLFLAG_RW | CTLTYPE_STRING, "debug.acpi.level", 0, acpi_debug_sysctl, "A", ""); #endif /* ACPI_DEBUG */ static int acpi_debug_objects_sysctl(SYSCTL_HANDLER_ARGS) { int error; int old; old = acpi_debug_objects; error = sysctl_handle_int(oidp, &acpi_debug_objects, 0, req); if (error != 0 || req->newptr == NULL) return (error); if (old == acpi_debug_objects || (old && acpi_debug_objects)) return (0); ACPI_SERIAL_BEGIN(acpi); AcpiGbl_EnableAmlDebugObject = acpi_debug_objects ? TRUE : FALSE; ACPI_SERIAL_END(acpi); return (0); } static int acpi_parse_interfaces(char *str, struct acpi_interface *iface) { char *p; size_t len; int i, j; p = str; while (isspace(*p) || *p == ',') p++; len = strlen(p); if (len == 0) return (0); p = strdup(p, M_TEMP); for (i = 0; i < len; i++) if (p[i] == ',') p[i] = '\0'; i = j = 0; while (i < len) if (isspace(p[i]) || p[i] == '\0') i++; else { i += strlen(p + i) + 1; j++; } if (j == 0) { free(p, M_TEMP); return (0); } iface->data = malloc(sizeof(*iface->data) * j, M_TEMP, M_WAITOK); iface->num = j; i = j = 0; while (i < len) if (isspace(p[i]) || p[i] == '\0') i++; else { iface->data[j] = p + i; i += strlen(p + i) + 1; j++; } return (j); } static void acpi_free_interfaces(struct acpi_interface *iface) { free(iface->data[0], M_TEMP); free(iface->data, M_TEMP); } static void acpi_reset_interfaces(device_t dev) { struct acpi_interface list; ACPI_STATUS status; int i; if (acpi_parse_interfaces(acpi_install_interface, &list) > 0) { for (i = 0; i < list.num; i++) { status = AcpiInstallInterface(list.data[i]); if (ACPI_FAILURE(status)) device_printf(dev, "failed to install _OSI(\"%s\"): %s\n", list.data[i], AcpiFormatException(status)); else if (bootverbose) device_printf(dev, "installed _OSI(\"%s\")\n", list.data[i]); } acpi_free_interfaces(&list); } if (acpi_parse_interfaces(acpi_remove_interface, &list) > 0) { for (i = 0; i < list.num; i++) { status = AcpiRemoveInterface(list.data[i]); if (ACPI_FAILURE(status)) device_printf(dev, "failed to remove _OSI(\"%s\"): %s\n", list.data[i], AcpiFormatException(status)); else if (bootverbose) device_printf(dev, "removed _OSI(\"%s\")\n", list.data[i]); } acpi_free_interfaces(&list); } } static int acpi_pm_func(u_long cmd, void *arg, ...) { int state, acpi_state; int error; struct acpi_softc *sc; va_list ap; error = 0; switch (cmd) { case POWER_CMD_SUSPEND: sc = (struct acpi_softc *)arg; if (sc == NULL) { error = EINVAL; goto out; } va_start(ap, arg); state = va_arg(ap, int); va_end(ap); switch (state) { case POWER_SLEEP_STATE_STANDBY: acpi_state = sc->acpi_standby_sx; break; case POWER_SLEEP_STATE_SUSPEND: acpi_state = sc->acpi_suspend_sx; break; case POWER_SLEEP_STATE_HIBERNATE: acpi_state = ACPI_STATE_S4; break; default: error = EINVAL; goto out; } if (ACPI_FAILURE(acpi_EnterSleepState(sc, acpi_state))) error = ENXIO; break; default: error = EINVAL; goto out; } out: return (error); } static void acpi_pm_register(void *arg) { if (!cold || resource_disabled("acpi", 0)) return; power_pm_register(POWER_PM_TYPE_ACPI, acpi_pm_func, NULL); } SYSINIT(power, SI_SUB_KLD, SI_ORDER_ANY, acpi_pm_register, NULL); Index: head/sys/dev/acpica/acpi_acad.c =================================================================== --- head/sys/dev/acpica/acpi_acad.c (revision 339753) +++ head/sys/dev/acpica/acpi_acad.c (revision 339754) @@ -1,283 +1,284 @@ /*- * Copyright (c) 2000 Takanori Watanabe * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_AC_ADAPTER ACPI_MODULE_NAME("AC_ADAPTER") /* Number of times to retry initialization before giving up. */ #define ACPI_ACAD_RETRY_MAX 6 #define ACPI_POWERSOURCE_STAT_CHANGE 0x80 struct acpi_acad_softc { int status; }; static void acpi_acad_get_status(void *); static void acpi_acad_notify_handler(ACPI_HANDLE, UINT32, void *); static int acpi_acad_probe(device_t); static int acpi_acad_attach(device_t); static int acpi_acad_ioctl(u_long, caddr_t, void *); static int acpi_acad_sysctl(SYSCTL_HANDLER_ARGS); static void acpi_acad_init_acline(void *arg); static void acpi_acad_ac_only(void *arg); static device_method_t acpi_acad_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_acad_probe), DEVMETHOD(device_attach, acpi_acad_attach), DEVMETHOD_END }; static driver_t acpi_acad_driver = { "acpi_acad", acpi_acad_methods, sizeof(struct acpi_acad_softc), }; static devclass_t acpi_acad_devclass; DRIVER_MODULE(acpi_acad, acpi, acpi_acad_driver, acpi_acad_devclass, 0, 0); MODULE_DEPEND(acpi_acad, acpi, 1, 1, 1); ACPI_SERIAL_DECL(acad, "ACPI AC adapter"); SYSINIT(acad, SI_SUB_KTHREAD_IDLE, SI_ORDER_FIRST, acpi_acad_ac_only, NULL); static void acpi_acad_get_status(void *context) { struct acpi_acad_softc *sc; device_t dev; ACPI_HANDLE h; int newstatus; dev = context; sc = device_get_softc(dev); h = acpi_get_handle(dev); newstatus = -1; acpi_GetInteger(h, "_PSR", &newstatus); /* If status is valid and has changed, notify the system. */ ACPI_SERIAL_BEGIN(acad); if (newstatus != -1 && sc->status != newstatus) { sc->status = newstatus; ACPI_SERIAL_END(acad); power_profile_set_state(newstatus ? POWER_PROFILE_PERFORMANCE : POWER_PROFILE_ECONOMY); ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "%s Line\n", newstatus ? "On" : "Off"); acpi_UserNotify("ACAD", h, newstatus); } else ACPI_SERIAL_END(acad); } static void acpi_acad_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) { device_t dev; dev = (device_t)context; switch (notify) { case ACPI_NOTIFY_BUS_CHECK: case ACPI_NOTIFY_DEVICE_CHECK: case ACPI_POWERSOURCE_STAT_CHANGE: /* Temporarily. It is better to notify policy manager */ AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_acad_get_status, context); break; default: device_printf(dev, "unknown notify %#x\n", notify); break; } } static int acpi_acad_probe(device_t dev) { static char *acad_ids[] = { "ACPI0003", NULL }; + int rv; - if (acpi_disabled("acad") || - ACPI_ID_PROBE(device_get_parent(dev), dev, acad_ids) == NULL) + if (acpi_disabled("acad")) return (ENXIO); - - device_set_desc(dev, "AC Adapter"); - return (0); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, acad_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "AC Adapter"); + return (rv); } static int acpi_acad_attach(device_t dev) { struct acpi_acad_softc *sc; struct acpi_softc *acpi_sc; ACPI_HANDLE handle; int error; sc = device_get_softc(dev); handle = acpi_get_handle(dev); error = acpi_register_ioctl(ACPIIO_ACAD_GET_STATUS, acpi_acad_ioctl, dev); if (error != 0) return (error); if (device_get_unit(dev) == 0) { acpi_sc = acpi_device_get_parent_softc(dev); SYSCTL_ADD_PROC(&acpi_sc->acpi_sysctl_ctx, SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "acline", CTLTYPE_INT | CTLFLAG_RD, &sc->status, 0, acpi_acad_sysctl, "I", ""); } /* Get initial status after whole system is up. */ sc->status = -1; /* * Install both system and device notify handlers since the Casio * FIVA needs them. */ AcpiInstallNotifyHandler(handle, ACPI_ALL_NOTIFY, acpi_acad_notify_handler, dev); AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_acad_init_acline, dev); return (0); } static int acpi_acad_ioctl(u_long cmd, caddr_t addr, void *arg) { struct acpi_acad_softc *sc; device_t dev; dev = (device_t)arg; sc = device_get_softc(dev); /* * No security check required: information retrieval only. If * new functions are added here, a check might be required. */ switch (cmd) { case ACPIIO_ACAD_GET_STATUS: acpi_acad_get_status(dev); *(int *)addr = sc->status; break; default: break; } return (0); } static int acpi_acad_sysctl(SYSCTL_HANDLER_ARGS) { int val, error; if (acpi_acad_get_acline(&val) != 0) return (ENXIO); val = *(u_int *)oidp->oid_arg1; error = sysctl_handle_int(oidp, &val, 0, req); return (error); } static void acpi_acad_init_acline(void *arg) { struct acpi_acad_softc *sc; device_t dev; int retry; dev = (device_t)arg; sc = device_get_softc(dev); ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "acline initialization start\n"); for (retry = 0; retry < ACPI_ACAD_RETRY_MAX; retry++) { acpi_acad_get_status(dev); if (sc->status != -1) break; AcpiOsSleep(10000); } ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "acline initialization done, tried %d times\n", retry + 1); } /* * If no AC line devices detected after boot, create an "online" event * so that userland code can adjust power settings accordingly. The default * power profile is "performance" so we don't need to repeat that here. */ static void acpi_acad_ac_only(void __unused *arg) { if (devclass_get_count(acpi_acad_devclass) == 0) acpi_UserNotify("ACAD", ACPI_ROOT_OBJECT, 1); } /* * Public interfaces. */ int acpi_acad_get_acline(int *status) { struct acpi_acad_softc *sc; device_t dev; dev = devclass_get_device(acpi_acad_devclass, 0); if (dev == NULL) return (ENXIO); sc = device_get_softc(dev); acpi_acad_get_status(dev); *status = sc->status; return (0); } Index: head/sys/dev/acpica/acpi_button.c =================================================================== --- head/sys/dev/acpica/acpi_button.c (revision 339753) +++ head/sys/dev/acpica/acpi_button.c (revision 339754) @@ -1,278 +1,281 @@ /*- * Copyright (c) 2000 Mitsaru IWASAKI * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_BUTTON ACPI_MODULE_NAME("BUTTON") struct acpi_button_softc { device_t button_dev; ACPI_HANDLE button_handle; boolean_t button_type; #define ACPI_POWER_BUTTON 0 #define ACPI_SLEEP_BUTTON 1 boolean_t fixed; }; #define ACPI_NOTIFY_BUTTON_PRESSED_FOR_SLEEP 0x80 #define ACPI_NOTIFY_BUTTON_PRESSED_FOR_WAKEUP 0x02 static int acpi_button_probe(device_t dev); static int acpi_button_attach(device_t dev); static int acpi_button_suspend(device_t dev); static int acpi_button_resume(device_t dev); static void acpi_button_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context); static ACPI_STATUS acpi_button_fixed_handler(void *context); static void acpi_button_notify_sleep(void *arg); static void acpi_button_notify_wakeup(void *arg); static char *btn_ids[] = { "PNP0C0C", "ACPI_FPB", "PNP0C0E", "ACPI_FSB", NULL }; static device_method_t acpi_button_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_button_probe), DEVMETHOD(device_attach, acpi_button_attach), DEVMETHOD(device_suspend, acpi_button_suspend), DEVMETHOD(device_shutdown, acpi_button_suspend), DEVMETHOD(device_resume, acpi_button_resume), DEVMETHOD_END }; static driver_t acpi_button_driver = { "acpi_button", acpi_button_methods, sizeof(struct acpi_button_softc), }; static devclass_t acpi_button_devclass; DRIVER_MODULE(acpi_button, acpi, acpi_button_driver, acpi_button_devclass, 0, 0); MODULE_DEPEND(acpi_button, acpi, 1, 1, 1); static int acpi_button_probe(device_t dev) { struct acpi_button_softc *sc; char *str; + int rv; - if (acpi_disabled("button") || - (str = ACPI_ID_PROBE(device_get_parent(dev), dev, btn_ids)) == NULL) + if (acpi_disabled("button")) return (ENXIO); - + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, btn_ids, &str); + if (rv > 0) + return (ENXIO); + sc = device_get_softc(dev); if (strcmp(str, "PNP0C0C") == 0) { device_set_desc(dev, "Power Button"); sc->button_type = ACPI_POWER_BUTTON; } else if (strcmp(str, "ACPI_FPB") == 0) { device_set_desc(dev, "Power Button (fixed)"); sc->button_type = ACPI_POWER_BUTTON; sc->fixed = 1; } else if (strcmp(str, "PNP0C0E") == 0) { device_set_desc(dev, "Sleep Button"); sc->button_type = ACPI_SLEEP_BUTTON; } else if (strcmp(str, "ACPI_FSB") == 0) { device_set_desc(dev, "Sleep Button (fixed)"); sc->button_type = ACPI_SLEEP_BUTTON; sc->fixed = 1; } - return (0); + return (rv); } static int acpi_button_attach(device_t dev) { struct acpi_prw_data prw; struct acpi_button_softc *sc; ACPI_STATUS status; int event; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->button_dev = dev; sc->button_handle = acpi_get_handle(dev); event = (sc->button_type == ACPI_SLEEP_BUTTON) ? ACPI_EVENT_SLEEP_BUTTON : ACPI_EVENT_POWER_BUTTON; /* * Install the new handler. We could remove any fixed handlers added * from the FADT once we have a duplicate from the AML but some systems * only return events on one or the other so we have to keep both. */ if (sc->fixed) { AcpiClearEvent(event); status = AcpiInstallFixedEventHandler(event, acpi_button_fixed_handler, sc); } else { /* * If a system does not get lid events, it may make sense to change * the type to ACPI_ALL_NOTIFY. Some systems generate both a wake * and runtime notify in that case though. */ status = AcpiInstallNotifyHandler(sc->button_handle, ACPI_DEVICE_NOTIFY, acpi_button_notify_handler, sc); } if (ACPI_FAILURE(status)) { device_printf(sc->button_dev, "couldn't install notify handler - %s\n", AcpiFormatException(status)); return_VALUE (ENXIO); } /* Enable the GPE for wake/runtime. */ acpi_wake_set_enable(dev, 1); if (acpi_parse_prw(sc->button_handle, &prw) == 0) AcpiEnableGpe(prw.gpe_handle, prw.gpe_bit); return_VALUE (0); } static int acpi_button_suspend(device_t dev) { return (0); } static int acpi_button_resume(device_t dev) { return (0); } static void acpi_button_notify_sleep(void *arg) { struct acpi_button_softc *sc; struct acpi_softc *acpi_sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_button_softc *)arg; acpi_sc = acpi_device_get_parent_softc(sc->button_dev); if (acpi_sc == NULL) return_VOID; acpi_UserNotify("Button", sc->button_handle, sc->button_type); switch (sc->button_type) { case ACPI_POWER_BUTTON: ACPI_VPRINT(sc->button_dev, acpi_sc, "power button pressed\n"); acpi_event_power_button_sleep(acpi_sc); break; case ACPI_SLEEP_BUTTON: ACPI_VPRINT(sc->button_dev, acpi_sc, "sleep button pressed\n"); acpi_event_sleep_button_sleep(acpi_sc); break; default: break; /* unknown button type */ } } static void acpi_button_notify_wakeup(void *arg) { struct acpi_button_softc *sc; struct acpi_softc *acpi_sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_button_softc *)arg; acpi_sc = acpi_device_get_parent_softc(sc->button_dev); if (acpi_sc == NULL) return_VOID; acpi_UserNotify("Button", sc->button_handle, sc->button_type); switch (sc->button_type) { case ACPI_POWER_BUTTON: ACPI_VPRINT(sc->button_dev, acpi_sc, "wakeup by power button\n"); acpi_event_power_button_wake(acpi_sc); break; case ACPI_SLEEP_BUTTON: ACPI_VPRINT(sc->button_dev, acpi_sc, "wakeup by sleep button\n"); acpi_event_sleep_button_wake(acpi_sc); break; default: break; /* unknown button type */ } } static void acpi_button_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_button_softc *sc; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); sc = (struct acpi_button_softc *)context; switch (notify) { case ACPI_NOTIFY_BUTTON_PRESSED_FOR_SLEEP: AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_button_notify_sleep, sc); break; case ACPI_NOTIFY_BUTTON_PRESSED_FOR_WAKEUP: AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_button_notify_wakeup, sc); break; default: device_printf(sc->button_dev, "unknown notify %#x\n", notify); break; } } static ACPI_STATUS acpi_button_fixed_handler(void *context) { struct acpi_button_softc *sc = (struct acpi_button_softc *)context; ACPI_FUNCTION_TRACE_PTR((char *)(uintptr_t)__func__, context); if (context == NULL) return_ACPI_STATUS (AE_BAD_PARAMETER); acpi_button_notify_handler(sc->button_handle, ACPI_NOTIFY_BUTTON_PRESSED_FOR_SLEEP, sc); return_ACPI_STATUS (AE_OK); } Index: head/sys/dev/acpica/acpi_cmbat.c =================================================================== --- head/sys/dev/acpica/acpi_cmbat.c (revision 339753) +++ head/sys/dev/acpica/acpi_cmbat.c (revision 339754) @@ -1,507 +1,508 @@ /*- * Copyright (c) 2005 Nate Lawson * Copyright (c) 2000 Munehiro Matsuda * Copyright (c) 2000 Takanori Watanabe * Copyright (c) 2000 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include static MALLOC_DEFINE(M_ACPICMBAT, "acpicmbat", "ACPI control method battery data"); /* Number of times to retry initialization before giving up. */ #define ACPI_CMBAT_RETRY_MAX 6 /* Check the battery once a minute. */ #define CMBAT_POLLRATE (60 * hz) /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_BATTERY ACPI_MODULE_NAME("BATTERY") #define ACPI_BATTERY_BST_CHANGE 0x80 #define ACPI_BATTERY_BIF_CHANGE 0x81 struct acpi_cmbat_softc { device_t dev; int flags; struct acpi_bif bif; struct acpi_bst bst; struct timespec bst_lastupdated; }; ACPI_SERIAL_DECL(cmbat, "ACPI cmbat"); static int acpi_cmbat_probe(device_t dev); static int acpi_cmbat_attach(device_t dev); static int acpi_cmbat_detach(device_t dev); static int acpi_cmbat_resume(device_t dev); static void acpi_cmbat_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context); static int acpi_cmbat_info_expired(struct timespec *lastupdated); static void acpi_cmbat_info_updated(struct timespec *lastupdated); static void acpi_cmbat_get_bst(void *arg); static void acpi_cmbat_get_bif_task(void *arg); static void acpi_cmbat_get_bif(void *arg); static int acpi_cmbat_bst(device_t dev, struct acpi_bst *bstp); static int acpi_cmbat_bif(device_t dev, struct acpi_bif *bifp); static void acpi_cmbat_init_battery(void *arg); static device_method_t acpi_cmbat_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_cmbat_probe), DEVMETHOD(device_attach, acpi_cmbat_attach), DEVMETHOD(device_detach, acpi_cmbat_detach), DEVMETHOD(device_resume, acpi_cmbat_resume), /* ACPI battery interface */ DEVMETHOD(acpi_batt_get_info, acpi_cmbat_bif), DEVMETHOD(acpi_batt_get_status, acpi_cmbat_bst), DEVMETHOD_END }; static driver_t acpi_cmbat_driver = { "battery", acpi_cmbat_methods, sizeof(struct acpi_cmbat_softc), }; static devclass_t acpi_cmbat_devclass; DRIVER_MODULE(acpi_cmbat, acpi, acpi_cmbat_driver, acpi_cmbat_devclass, 0, 0); MODULE_DEPEND(acpi_cmbat, acpi, 1, 1, 1); static int acpi_cmbat_probe(device_t dev) { static char *cmbat_ids[] = { "PNP0C0A", NULL }; - - if (acpi_disabled("cmbat") || - ACPI_ID_PROBE(device_get_parent(dev), dev, cmbat_ids) == NULL) + int rv; + + if (acpi_disabled("cmbat")) return (ENXIO); - - device_set_desc(dev, "ACPI Control Method Battery"); - return (0); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, cmbat_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "ACPI Control Method Battery"); + return (rv); } static int acpi_cmbat_attach(device_t dev) { int error; ACPI_HANDLE handle; struct acpi_cmbat_softc *sc; sc = device_get_softc(dev); handle = acpi_get_handle(dev); sc->dev = dev; timespecclear(&sc->bst_lastupdated); error = acpi_battery_register(dev); if (error != 0) { device_printf(dev, "registering battery failed\n"); return (error); } /* * Install a system notify handler in addition to the device notify. * Toshiba notebook uses this alternate notify for its battery. */ AcpiInstallNotifyHandler(handle, ACPI_ALL_NOTIFY, acpi_cmbat_notify_handler, dev); AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_cmbat_init_battery, dev); return (0); } static int acpi_cmbat_detach(device_t dev) { ACPI_HANDLE handle; handle = acpi_get_handle(dev); AcpiRemoveNotifyHandler(handle, ACPI_ALL_NOTIFY, acpi_cmbat_notify_handler); acpi_battery_remove(dev); /* * Force any pending notification handler calls to complete by * requesting cmbat serialisation while freeing and clearing the * softc pointer: */ ACPI_SERIAL_BEGIN(cmbat); device_set_softc(dev, NULL); ACPI_SERIAL_END(cmbat); return (0); } static int acpi_cmbat_resume(device_t dev) { AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_cmbat_init_battery, dev); return (0); } static void acpi_cmbat_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_cmbat_softc *sc; device_t dev; dev = (device_t)context; sc = device_get_softc(dev); switch (notify) { case ACPI_NOTIFY_DEVICE_CHECK: case ACPI_BATTERY_BST_CHANGE: /* * Clear the last updated time. The next call to retrieve the * battery status will get the new value for us. */ timespecclear(&sc->bst_lastupdated); break; case ACPI_NOTIFY_BUS_CHECK: case ACPI_BATTERY_BIF_CHANGE: /* * Queue a callback to get the current battery info from thread * context. It's not safe to block in a notify handler. */ AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_cmbat_get_bif_task, dev); break; } acpi_UserNotify("CMBAT", h, notify); } static int acpi_cmbat_info_expired(struct timespec *lastupdated) { struct timespec curtime; ACPI_SERIAL_ASSERT(cmbat); if (lastupdated == NULL) return (TRUE); if (!timespecisset(lastupdated)) return (TRUE); getnanotime(&curtime); timespecsub(&curtime, lastupdated, &curtime); return (curtime.tv_sec < 0 || curtime.tv_sec > acpi_battery_get_info_expire()); } static void acpi_cmbat_info_updated(struct timespec *lastupdated) { ACPI_SERIAL_ASSERT(cmbat); if (lastupdated != NULL) getnanotime(lastupdated); } static void acpi_cmbat_get_bst(void *arg) { struct acpi_cmbat_softc *sc; ACPI_STATUS as; ACPI_OBJECT *res; ACPI_HANDLE h; ACPI_BUFFER bst_buffer; device_t dev; ACPI_SERIAL_ASSERT(cmbat); dev = arg; sc = device_get_softc(dev); h = acpi_get_handle(dev); bst_buffer.Pointer = NULL; bst_buffer.Length = ACPI_ALLOCATE_BUFFER; if (!acpi_cmbat_info_expired(&sc->bst_lastupdated)) goto end; as = AcpiEvaluateObject(h, "_BST", NULL, &bst_buffer); if (ACPI_FAILURE(as)) { ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "error fetching current battery status -- %s\n", AcpiFormatException(as)); goto end; } res = (ACPI_OBJECT *)bst_buffer.Pointer; if (!ACPI_PKG_VALID(res, 4)) { ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "battery status corrupted\n"); goto end; } if (acpi_PkgInt32(res, 0, &sc->bst.state) != 0) goto end; if (acpi_PkgInt32(res, 1, &sc->bst.rate) != 0) goto end; if (acpi_PkgInt32(res, 2, &sc->bst.cap) != 0) goto end; if (acpi_PkgInt32(res, 3, &sc->bst.volt) != 0) goto end; acpi_cmbat_info_updated(&sc->bst_lastupdated); /* Clear out undefined/extended bits that might be set by hardware. */ sc->bst.state &= ACPI_BATT_STAT_BST_MASK; if ((sc->bst.state & ACPI_BATT_STAT_INVALID) == ACPI_BATT_STAT_INVALID) ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "battery reports simultaneous charging and discharging\n"); /* XXX If all batteries are critical, perhaps we should suspend. */ if (sc->bst.state & ACPI_BATT_STAT_CRITICAL) { if ((sc->flags & ACPI_BATT_STAT_CRITICAL) == 0) { sc->flags |= ACPI_BATT_STAT_CRITICAL; device_printf(dev, "critically low charge!\n"); } } else sc->flags &= ~ACPI_BATT_STAT_CRITICAL; end: if (bst_buffer.Pointer != NULL) AcpiOsFree(bst_buffer.Pointer); } /* XXX There should be a cleaner way to do this locking. */ static void acpi_cmbat_get_bif_task(void *arg) { ACPI_SERIAL_BEGIN(cmbat); acpi_cmbat_get_bif(arg); ACPI_SERIAL_END(cmbat); } static void acpi_cmbat_get_bif(void *arg) { struct acpi_cmbat_softc *sc; ACPI_STATUS as; ACPI_OBJECT *res; ACPI_HANDLE h; ACPI_BUFFER bif_buffer; device_t dev; ACPI_SERIAL_ASSERT(cmbat); dev = arg; sc = device_get_softc(dev); h = acpi_get_handle(dev); bif_buffer.Pointer = NULL; bif_buffer.Length = ACPI_ALLOCATE_BUFFER; as = AcpiEvaluateObject(h, "_BIF", NULL, &bif_buffer); if (ACPI_FAILURE(as)) { ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "error fetching current battery info -- %s\n", AcpiFormatException(as)); goto end; } res = (ACPI_OBJECT *)bif_buffer.Pointer; if (!ACPI_PKG_VALID(res, 13)) { ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "battery info corrupted\n"); goto end; } if (acpi_PkgInt32(res, 0, &sc->bif.units) != 0) goto end; if (acpi_PkgInt32(res, 1, &sc->bif.dcap) != 0) goto end; if (acpi_PkgInt32(res, 2, &sc->bif.lfcap) != 0) goto end; if (acpi_PkgInt32(res, 3, &sc->bif.btech) != 0) goto end; if (acpi_PkgInt32(res, 4, &sc->bif.dvol) != 0) goto end; if (acpi_PkgInt32(res, 5, &sc->bif.wcap) != 0) goto end; if (acpi_PkgInt32(res, 6, &sc->bif.lcap) != 0) goto end; if (acpi_PkgInt32(res, 7, &sc->bif.gra1) != 0) goto end; if (acpi_PkgInt32(res, 8, &sc->bif.gra2) != 0) goto end; if (acpi_PkgStr(res, 9, sc->bif.model, ACPI_CMBAT_MAXSTRLEN) != 0) goto end; if (acpi_PkgStr(res, 10, sc->bif.serial, ACPI_CMBAT_MAXSTRLEN) != 0) goto end; if (acpi_PkgStr(res, 11, sc->bif.type, ACPI_CMBAT_MAXSTRLEN) != 0) goto end; if (acpi_PkgStr(res, 12, sc->bif.oeminfo, ACPI_CMBAT_MAXSTRLEN) != 0) goto end; end: if (bif_buffer.Pointer != NULL) AcpiOsFree(bif_buffer.Pointer); } static int acpi_cmbat_bif(device_t dev, struct acpi_bif *bifp) { struct acpi_cmbat_softc *sc; sc = device_get_softc(dev); /* * Just copy the data. The only value that should change is the * last-full capacity, so we only update when we get a notify that says * the info has changed. Many systems apparently take a long time to * process a _BIF call so we avoid it if possible. */ ACPI_SERIAL_BEGIN(cmbat); bifp->units = sc->bif.units; bifp->dcap = sc->bif.dcap; bifp->lfcap = sc->bif.lfcap; bifp->btech = sc->bif.btech; bifp->dvol = sc->bif.dvol; bifp->wcap = sc->bif.wcap; bifp->lcap = sc->bif.lcap; bifp->gra1 = sc->bif.gra1; bifp->gra2 = sc->bif.gra2; strncpy(bifp->model, sc->bif.model, sizeof(sc->bif.model)); strncpy(bifp->serial, sc->bif.serial, sizeof(sc->bif.serial)); strncpy(bifp->type, sc->bif.type, sizeof(sc->bif.type)); strncpy(bifp->oeminfo, sc->bif.oeminfo, sizeof(sc->bif.oeminfo)); ACPI_SERIAL_END(cmbat); return (0); } static int acpi_cmbat_bst(device_t dev, struct acpi_bst *bstp) { struct acpi_cmbat_softc *sc; sc = device_get_softc(dev); ACPI_SERIAL_BEGIN(cmbat); if (acpi_BatteryIsPresent(dev)) { acpi_cmbat_get_bst(dev); bstp->state = sc->bst.state; bstp->rate = sc->bst.rate; bstp->cap = sc->bst.cap; bstp->volt = sc->bst.volt; } else bstp->state = ACPI_BATT_STAT_NOT_PRESENT; ACPI_SERIAL_END(cmbat); return (0); } static void acpi_cmbat_init_battery(void *arg) { struct acpi_cmbat_softc *sc; int retry, valid; device_t dev; dev = (device_t)arg; ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "battery initialization start\n"); /* * Try repeatedly to get valid data from the battery. Since the * embedded controller isn't always ready just after boot, we may have * to wait a while. */ for (retry = 0; retry < ACPI_CMBAT_RETRY_MAX; retry++, AcpiOsSleep(10000)) { /* * Batteries on DOCK can be ejected w/ DOCK during retrying. * * If there is a valid softc pointer the device may be in * attaching, attached or detaching state. If the state is * different from attached retry getting the device state * until it becomes stable. This solves a race if the ACPI * notification handler is called during attach, because * device_is_attached() doesn't return non-zero until after * the attach code has been executed. */ ACPI_SERIAL_BEGIN(cmbat); sc = device_get_softc(dev); if (sc == NULL) { ACPI_SERIAL_END(cmbat); return; } if (!acpi_BatteryIsPresent(dev) || !device_is_attached(dev)) { ACPI_SERIAL_END(cmbat); continue; } /* * Only query the battery if this is the first try or the specific * type of info is still invalid. */ if (retry == 0 || !acpi_battery_bst_valid(&sc->bst)) { timespecclear(&sc->bst_lastupdated); acpi_cmbat_get_bst(dev); } if (retry == 0 || !acpi_battery_bif_valid(&sc->bif)) acpi_cmbat_get_bif(dev); valid = acpi_battery_bst_valid(&sc->bst) && acpi_battery_bif_valid(&sc->bif); ACPI_SERIAL_END(cmbat); if (valid) break; } if (retry == ACPI_CMBAT_RETRY_MAX) { ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "battery initialization failed, giving up\n"); } else { ACPI_VPRINT(dev, acpi_device_get_parent_softc(dev), "battery initialization done, tried %d times\n", retry + 1); } } Index: head/sys/dev/acpica/acpi_container.c =================================================================== --- head/sys/dev/acpica/acpi_container.c (revision 339753) +++ head/sys/dev/acpica/acpi_container.c (revision 339754) @@ -1,166 +1,167 @@ /*- * Copyright (c) 2017 Microsoft Corp. * 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 unmodified, 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 ``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 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include "pcib_if.h" ACPI_MODULE_NAME("CONTAINER") static int acpi_syscont_probe(device_t); static int acpi_syscont_attach(device_t); static int acpi_syscont_detach(device_t); static int acpi_syscont_alloc_msi(device_t, device_t, int count, int maxcount, int *irqs); static int acpi_syscont_release_msi(device_t bus, device_t dev, int count, int *irqs); static int acpi_syscont_alloc_msix(device_t bus, device_t dev, int *irq); static int acpi_syscont_release_msix(device_t bus, device_t dev, int irq); static int acpi_syscont_map_msi(device_t bus, device_t dev, int irq, uint64_t *addr, uint32_t *data); static device_method_t acpi_syscont_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_syscont_probe), DEVMETHOD(device_attach, acpi_syscont_attach), DEVMETHOD(device_detach, acpi_syscont_detach), /* Bus interface */ DEVMETHOD(bus_add_child, bus_generic_add_child), DEVMETHOD(bus_print_child, bus_generic_print_child), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), #if __FreeBSD_version >= 1100000 DEVMETHOD(bus_get_cpus, bus_generic_get_cpus), #endif /* pcib interface */ DEVMETHOD(pcib_alloc_msi, acpi_syscont_alloc_msi), DEVMETHOD(pcib_release_msi, acpi_syscont_release_msi), DEVMETHOD(pcib_alloc_msix, acpi_syscont_alloc_msix), DEVMETHOD(pcib_release_msix, acpi_syscont_release_msix), DEVMETHOD(pcib_map_msi, acpi_syscont_map_msi), DEVMETHOD_END }; static driver_t acpi_syscont_driver = { "acpi_syscontainer", acpi_syscont_methods, 0, }; static devclass_t acpi_syscont_devclass; DRIVER_MODULE(acpi_syscontainer, acpi, acpi_syscont_driver, acpi_syscont_devclass, NULL, NULL); MODULE_DEPEND(acpi_syscontainer, acpi, 1, 1, 1); static int acpi_syscont_probe(device_t dev) { static char *syscont_ids[] = { "ACPI0004", "PNP0A05", "PNP0A06", NULL }; + int rv; - if (acpi_disabled("syscontainer") || - ACPI_ID_PROBE(device_get_parent(dev), dev, syscont_ids) == NULL) + if (acpi_disabled("syscontainer")) return (ENXIO); - - device_set_desc(dev, "System Container"); - return (BUS_PROBE_DEFAULT); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, syscont_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "System Container"); + return (rv); } static int acpi_syscont_attach(device_t dev) { bus_generic_probe(dev); return (bus_generic_attach(dev)); } static int acpi_syscont_detach(device_t dev) { return (bus_generic_detach(dev)); } static int acpi_syscont_alloc_msi(device_t bus, device_t dev, int count, int maxcount, int *irqs) { device_t parent = device_get_parent(bus); return (PCIB_ALLOC_MSI(device_get_parent(parent), dev, count, maxcount, irqs)); } static int acpi_syscont_release_msi(device_t bus, device_t dev, int count, int *irqs) { device_t parent = device_get_parent(bus); return (PCIB_RELEASE_MSI(device_get_parent(parent), dev, count, irqs)); } static int acpi_syscont_alloc_msix(device_t bus, device_t dev, int *irq) { device_t parent = device_get_parent(bus); return (PCIB_ALLOC_MSIX(device_get_parent(parent), dev, irq)); } static int acpi_syscont_release_msix(device_t bus, device_t dev, int irq) { device_t parent = device_get_parent(bus); return (PCIB_RELEASE_MSIX(device_get_parent(parent), dev, irq)); } static int acpi_syscont_map_msi(device_t bus, device_t dev, int irq, uint64_t *addr, uint32_t *data) { device_t parent = device_get_parent(bus); return (PCIB_MAP_MSI(device_get_parent(parent), dev, irq, addr, data)); } Index: head/sys/dev/acpica/acpi_cpu.c =================================================================== --- head/sys/dev/acpica/acpi_cpu.c (revision 339753) +++ head/sys/dev/acpica/acpi_cpu.c (revision 339754) @@ -1,1566 +1,1566 @@ /*- * Copyright (c) 2003-2005 Nate Lawson (SDG) * Copyright (c) 2001 Michael Smith * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(__amd64__) || defined(__i386__) #include #include #include #endif #include #include #include #include /* * Support for ACPI Processor devices, including C[1-3] sleep states. */ /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_PROCESSOR ACPI_MODULE_NAME("PROCESSOR") struct acpi_cx { struct resource *p_lvlx; /* Register to read to enter state. */ uint32_t type; /* C1-3 (C4 and up treated as C3). */ uint32_t trans_lat; /* Transition latency (usec). */ uint32_t power; /* Power consumed (mW). */ int res_type; /* Resource type for p_lvlx. */ int res_rid; /* Resource ID for p_lvlx. */ bool do_mwait; uint32_t mwait_hint; bool mwait_hw_coord; bool mwait_bm_avoidance; }; #define MAX_CX_STATES 8 struct acpi_cpu_softc { device_t cpu_dev; ACPI_HANDLE cpu_handle; struct pcpu *cpu_pcpu; uint32_t cpu_acpi_id; /* ACPI processor id */ uint32_t cpu_p_blk; /* ACPI P_BLK location */ uint32_t cpu_p_blk_len; /* P_BLK length (must be 6). */ struct acpi_cx cpu_cx_states[MAX_CX_STATES]; int cpu_cx_count; /* Number of valid Cx states. */ int cpu_prev_sleep;/* Last idle sleep duration. */ int cpu_features; /* Child driver supported features. */ /* Runtime state. */ int cpu_non_c2; /* Index of lowest non-C2 state. */ int cpu_non_c3; /* Index of lowest non-C3 state. */ u_int cpu_cx_stats[MAX_CX_STATES];/* Cx usage history. */ /* Values for sysctl. */ struct sysctl_ctx_list cpu_sysctl_ctx; struct sysctl_oid *cpu_sysctl_tree; int cpu_cx_lowest; int cpu_cx_lowest_lim; int cpu_disable_idle; /* Disable entry to idle function */ char cpu_cx_supported[64]; }; struct acpi_cpu_device { struct resource_list ad_rl; }; #define CPU_GET_REG(reg, width) \ (bus_space_read_ ## width(rman_get_bustag((reg)), \ rman_get_bushandle((reg)), 0)) #define CPU_SET_REG(reg, width, val) \ (bus_space_write_ ## width(rman_get_bustag((reg)), \ rman_get_bushandle((reg)), 0, (val))) #define PM_USEC(x) ((x) >> 2) /* ~4 clocks per usec (3.57955 Mhz) */ #define ACPI_NOTIFY_CX_STATES 0x81 /* _CST changed. */ #define CPU_QUIRK_NO_C3 (1<<0) /* C3-type states are not usable. */ #define CPU_QUIRK_NO_BM_CTRL (1<<2) /* No bus mastering control. */ #define PCI_VENDOR_INTEL 0x8086 #define PCI_DEVICE_82371AB_3 0x7113 /* PIIX4 chipset for quirks. */ #define PCI_REVISION_A_STEP 0 #define PCI_REVISION_B_STEP 1 #define PCI_REVISION_4E 2 #define PCI_REVISION_4M 3 #define PIIX4_DEVACTB_REG 0x58 #define PIIX4_BRLD_EN_IRQ0 (1<<0) #define PIIX4_BRLD_EN_IRQ (1<<1) #define PIIX4_BRLD_EN_IRQ8 (1<<5) #define PIIX4_STOP_BREAK_MASK (PIIX4_BRLD_EN_IRQ0 | PIIX4_BRLD_EN_IRQ | PIIX4_BRLD_EN_IRQ8) #define PIIX4_PCNTRL_BST_EN (1<<10) #define CST_FFH_VENDOR_INTEL 1 #define CST_FFH_INTEL_CL_C1IO 1 #define CST_FFH_INTEL_CL_MWAIT 2 #define CST_FFH_MWAIT_HW_COORD 0x0001 #define CST_FFH_MWAIT_BM_AVOID 0x0002 #define CPUDEV_DEVICE_ID "ACPI0007" /* Allow users to ignore processor orders in MADT. */ static int cpu_unordered; SYSCTL_INT(_debug_acpi, OID_AUTO, cpu_unordered, CTLFLAG_RDTUN, &cpu_unordered, 0, "Do not use the MADT to match ACPI Processor objects to CPUs."); /* Knob to disable acpi_cpu devices */ bool acpi_cpu_disabled = false; /* Platform hardware resource information. */ static uint32_t cpu_smi_cmd; /* Value to write to SMI_CMD. */ static uint8_t cpu_cst_cnt; /* Indicate we are _CST aware. */ static int cpu_quirks; /* Indicate any hardware bugs. */ /* Values for sysctl. */ static struct sysctl_ctx_list cpu_sysctl_ctx; static struct sysctl_oid *cpu_sysctl_tree; static int cpu_cx_generic; static int cpu_cx_lowest_lim; static device_t *cpu_devices; static int cpu_ndevices; static struct acpi_cpu_softc **cpu_softc; ACPI_SERIAL_DECL(cpu, "ACPI CPU"); static int acpi_cpu_probe(device_t dev); static int acpi_cpu_attach(device_t dev); static int acpi_cpu_suspend(device_t dev); static int acpi_cpu_resume(device_t dev); static int acpi_pcpu_get_id(device_t dev, uint32_t *acpi_id, uint32_t *cpu_id); static struct resource_list *acpi_cpu_get_rlist(device_t dev, device_t child); static device_t acpi_cpu_add_child(device_t dev, u_int order, const char *name, int unit); static int acpi_cpu_read_ivar(device_t dev, device_t child, int index, uintptr_t *result); static int acpi_cpu_shutdown(device_t dev); static void acpi_cpu_cx_probe(struct acpi_cpu_softc *sc); static void acpi_cpu_generic_cx_probe(struct acpi_cpu_softc *sc); static int acpi_cpu_cx_cst(struct acpi_cpu_softc *sc); static void acpi_cpu_startup(void *arg); static void acpi_cpu_startup_cx(struct acpi_cpu_softc *sc); static void acpi_cpu_cx_list(struct acpi_cpu_softc *sc); #if defined(__i386__) || defined(__amd64__) static void acpi_cpu_idle(sbintime_t sbt); #endif static void acpi_cpu_notify(ACPI_HANDLE h, UINT32 notify, void *context); static void acpi_cpu_quirks(void); static void acpi_cpu_quirks_piix4(void); static int acpi_cpu_usage_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_cpu_usage_counters_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_cpu_set_cx_lowest(struct acpi_cpu_softc *sc); static int acpi_cpu_cx_lowest_sysctl(SYSCTL_HANDLER_ARGS); static int acpi_cpu_global_cx_lowest_sysctl(SYSCTL_HANDLER_ARGS); #if defined(__i386__) || defined(__amd64__) static int acpi_cpu_method_sysctl(SYSCTL_HANDLER_ARGS); #endif static device_method_t acpi_cpu_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_cpu_probe), DEVMETHOD(device_attach, acpi_cpu_attach), DEVMETHOD(device_detach, bus_generic_detach), DEVMETHOD(device_shutdown, acpi_cpu_shutdown), DEVMETHOD(device_suspend, acpi_cpu_suspend), DEVMETHOD(device_resume, acpi_cpu_resume), /* Bus interface */ DEVMETHOD(bus_add_child, acpi_cpu_add_child), DEVMETHOD(bus_read_ivar, acpi_cpu_read_ivar), DEVMETHOD(bus_get_resource_list, acpi_cpu_get_rlist), DEVMETHOD(bus_get_resource, bus_generic_rl_get_resource), DEVMETHOD(bus_set_resource, bus_generic_rl_set_resource), DEVMETHOD(bus_alloc_resource, bus_generic_rl_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_rl_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD_END }; static driver_t acpi_cpu_driver = { "cpu", acpi_cpu_methods, sizeof(struct acpi_cpu_softc), }; static devclass_t acpi_cpu_devclass; DRIVER_MODULE(cpu, acpi, acpi_cpu_driver, acpi_cpu_devclass, 0, 0); MODULE_DEPEND(cpu, acpi, 1, 1, 1); static int acpi_cpu_probe(device_t dev) { static char *cpudev_ids[] = { CPUDEV_DEVICE_ID, NULL }; int acpi_id, cpu_id; ACPI_BUFFER buf; ACPI_HANDLE handle; ACPI_OBJECT *obj; ACPI_STATUS status; ACPI_OBJECT_TYPE type; if (acpi_disabled("cpu") || acpi_cpu_disabled) return (ENXIO); type = acpi_get_type(dev); if (type != ACPI_TYPE_PROCESSOR && type != ACPI_TYPE_DEVICE) return (ENXIO); if (type == ACPI_TYPE_DEVICE && - ACPI_ID_PROBE(device_get_parent(dev), dev, cpudev_ids) == NULL) + ACPI_ID_PROBE(device_get_parent(dev), dev, cpudev_ids, NULL) >= 0) return (ENXIO); handle = acpi_get_handle(dev); if (cpu_softc == NULL) cpu_softc = malloc(sizeof(struct acpi_cpu_softc *) * (mp_maxid + 1), M_TEMP /* XXX */, M_WAITOK | M_ZERO); if (type == ACPI_TYPE_PROCESSOR) { /* Get our Processor object. */ buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObject(handle, NULL, NULL, &buf); if (ACPI_FAILURE(status)) { device_printf(dev, "probe failed to get Processor obj - %s\n", AcpiFormatException(status)); return (ENXIO); } obj = (ACPI_OBJECT *)buf.Pointer; if (obj->Type != ACPI_TYPE_PROCESSOR) { device_printf(dev, "Processor object has bad type %d\n", obj->Type); AcpiOsFree(obj); return (ENXIO); } /* * Find the processor associated with our unit. We could use the * ProcId as a key, however, some boxes do not have the same values * in their Processor object as the ProcId values in the MADT. */ acpi_id = obj->Processor.ProcId; AcpiOsFree(obj); } else { status = acpi_GetInteger(handle, "_UID", &acpi_id); if (ACPI_FAILURE(status)) { device_printf(dev, "Device object has bad value - %s\n", AcpiFormatException(status)); return (ENXIO); } } if (acpi_pcpu_get_id(dev, &acpi_id, &cpu_id) != 0) return (ENXIO); /* * Check if we already probed this processor. We scan the bus twice * so it's possible we've already seen this one. */ if (cpu_softc[cpu_id] != NULL) return (ENXIO); /* Mark this processor as in-use and save our derived id for attach. */ cpu_softc[cpu_id] = (void *)1; acpi_set_private(dev, (void*)(intptr_t)cpu_id); device_set_desc(dev, "ACPI CPU"); if (!bootverbose && device_get_unit(dev) != 0) { device_quiet(dev); device_quiet_children(dev); } return (0); } static int acpi_cpu_attach(device_t dev) { ACPI_BUFFER buf; ACPI_OBJECT arg, *obj; ACPI_OBJECT_LIST arglist; struct pcpu *pcpu_data; struct acpi_cpu_softc *sc; struct acpi_softc *acpi_sc; ACPI_STATUS status; u_int features; int cpu_id, drv_count, i; driver_t **drivers; uint32_t cap_set[3]; /* UUID needed by _OSC evaluation */ static uint8_t cpu_oscuuid[16] = { 0x16, 0xA6, 0x77, 0x40, 0x0C, 0x29, 0xBE, 0x47, 0x9E, 0xBD, 0xD8, 0x70, 0x58, 0x71, 0x39, 0x53 }; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->cpu_dev = dev; sc->cpu_handle = acpi_get_handle(dev); cpu_id = (int)(intptr_t)acpi_get_private(dev); cpu_softc[cpu_id] = sc; pcpu_data = pcpu_find(cpu_id); pcpu_data->pc_device = dev; sc->cpu_pcpu = pcpu_data; cpu_smi_cmd = AcpiGbl_FADT.SmiCommand; cpu_cst_cnt = AcpiGbl_FADT.CstControl; if (acpi_get_type(dev) == ACPI_TYPE_PROCESSOR) { buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObject(sc->cpu_handle, NULL, NULL, &buf); if (ACPI_FAILURE(status)) { device_printf(dev, "attach failed to get Processor obj - %s\n", AcpiFormatException(status)); return (ENXIO); } obj = (ACPI_OBJECT *)buf.Pointer; sc->cpu_p_blk = obj->Processor.PblkAddress; sc->cpu_p_blk_len = obj->Processor.PblkLength; sc->cpu_acpi_id = obj->Processor.ProcId; AcpiOsFree(obj); } else { KASSERT(acpi_get_type(dev) == ACPI_TYPE_DEVICE, ("Unexpected ACPI object")); status = acpi_GetInteger(sc->cpu_handle, "_UID", &sc->cpu_acpi_id); if (ACPI_FAILURE(status)) { device_printf(dev, "Device object has bad value - %s\n", AcpiFormatException(status)); return (ENXIO); } sc->cpu_p_blk = 0; sc->cpu_p_blk_len = 0; } ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu%d: P_BLK at %#x/%d\n", device_get_unit(dev), sc->cpu_p_blk, sc->cpu_p_blk_len)); /* * If this is the first cpu we attach, create and initialize the generic * resources that will be used by all acpi cpu devices. */ if (device_get_unit(dev) == 0) { /* Assume we won't be using generic Cx mode by default */ cpu_cx_generic = FALSE; /* Install hw.acpi.cpu sysctl tree */ acpi_sc = acpi_device_get_parent_softc(dev); sysctl_ctx_init(&cpu_sysctl_ctx); cpu_sysctl_tree = SYSCTL_ADD_NODE(&cpu_sysctl_ctx, SYSCTL_CHILDREN(acpi_sc->acpi_sysctl_tree), OID_AUTO, "cpu", CTLFLAG_RD, 0, "node for CPU children"); } /* * Before calling any CPU methods, collect child driver feature hints * and notify ACPI of them. We support unified SMP power control * so advertise this ourselves. Note this is not the same as independent * SMP control where each CPU can have different settings. */ sc->cpu_features = ACPI_CAP_SMP_SAME | ACPI_CAP_SMP_SAME_C3 | ACPI_CAP_C1_IO_HALT; #if defined(__i386__) || defined(__amd64__) /* * Ask for MWAIT modes if not disabled and interrupts work * reasonable with MWAIT. */ if (!acpi_disabled("mwait") && cpu_mwait_usable()) sc->cpu_features |= ACPI_CAP_SMP_C1_NATIVE | ACPI_CAP_SMP_C3_NATIVE; #endif if (devclass_get_drivers(acpi_cpu_devclass, &drivers, &drv_count) == 0) { for (i = 0; i < drv_count; i++) { if (ACPI_GET_FEATURES(drivers[i], &features) == 0) sc->cpu_features |= features; } free(drivers, M_TEMP); } /* * CPU capabilities are specified in * Intel Processor Vendor-Specific ACPI Interface Specification. */ if (sc->cpu_features) { cap_set[1] = sc->cpu_features; status = acpi_EvaluateOSC(sc->cpu_handle, cpu_oscuuid, 1, 2, cap_set, cap_set, false); if (ACPI_SUCCESS(status)) { if (cap_set[0] != 0) device_printf(dev, "_OSC returned status %#x\n", cap_set[0]); } else { arglist.Pointer = &arg; arglist.Count = 1; arg.Type = ACPI_TYPE_BUFFER; arg.Buffer.Length = sizeof(cap_set); arg.Buffer.Pointer = (uint8_t *)cap_set; cap_set[0] = 1; /* revision */ cap_set[1] = 1; /* number of capabilities integers */ cap_set[2] = sc->cpu_features; AcpiEvaluateObject(sc->cpu_handle, "_PDC", &arglist, NULL); } } /* Probe for Cx state support. */ acpi_cpu_cx_probe(sc); return (0); } static void acpi_cpu_postattach(void *unused __unused) { device_t *devices; int err; int i, n; int attached; err = devclass_get_devices(acpi_cpu_devclass, &devices, &n); if (err != 0) { printf("devclass_get_devices(acpi_cpu_devclass) failed\n"); return; } attached = 0; for (i = 0; i < n; i++) if (device_is_attached(devices[i]) && device_get_driver(devices[i]) == &acpi_cpu_driver) attached = 1; for (i = 0; i < n; i++) bus_generic_probe(devices[i]); for (i = 0; i < n; i++) bus_generic_attach(devices[i]); free(devices, M_TEMP); if (attached) { #ifdef EARLY_AP_STARTUP acpi_cpu_startup(NULL); #else /* Queue post cpu-probing task handler */ AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_cpu_startup, NULL); #endif } } SYSINIT(acpi_cpu, SI_SUB_CONFIGURE, SI_ORDER_MIDDLE, acpi_cpu_postattach, NULL); static void disable_idle(struct acpi_cpu_softc *sc) { cpuset_t cpuset; CPU_SETOF(sc->cpu_pcpu->pc_cpuid, &cpuset); sc->cpu_disable_idle = TRUE; /* * Ensure that the CPU is not in idle state or in acpi_cpu_idle(). * Note that this code depends on the fact that the rendezvous IPI * can not penetrate context where interrupts are disabled and acpi_cpu_idle * is called and executed in such a context with interrupts being re-enabled * right before return. */ smp_rendezvous_cpus(cpuset, smp_no_rendezvous_barrier, NULL, smp_no_rendezvous_barrier, NULL); } static void enable_idle(struct acpi_cpu_softc *sc) { sc->cpu_disable_idle = FALSE; } #if defined(__i386__) || defined(__amd64__) static int is_idle_disabled(struct acpi_cpu_softc *sc) { return (sc->cpu_disable_idle); } #endif /* * Disable any entry to the idle function during suspend and re-enable it * during resume. */ static int acpi_cpu_suspend(device_t dev) { int error; error = bus_generic_suspend(dev); if (error) return (error); disable_idle(device_get_softc(dev)); return (0); } static int acpi_cpu_resume(device_t dev) { enable_idle(device_get_softc(dev)); return (bus_generic_resume(dev)); } /* * Find the processor associated with a given ACPI ID. By default, * use the MADT to map ACPI IDs to APIC IDs and use that to locate a * processor. Some systems have inconsistent ASL and MADT however. * For these systems the cpu_unordered tunable can be set in which * case we assume that Processor objects are listed in the same order * in both the MADT and ASL. */ static int acpi_pcpu_get_id(device_t dev, uint32_t *acpi_id, uint32_t *cpu_id) { struct pcpu *pc; uint32_t i, idx; KASSERT(acpi_id != NULL, ("Null acpi_id")); KASSERT(cpu_id != NULL, ("Null cpu_id")); idx = device_get_unit(dev); /* * If pc_acpi_id for CPU 0 is not initialized (e.g. a non-APIC * UP box) use the ACPI ID from the first processor we find. */ if (idx == 0 && mp_ncpus == 1) { pc = pcpu_find(0); if (pc->pc_acpi_id == 0xffffffff) pc->pc_acpi_id = *acpi_id; *cpu_id = 0; return (0); } CPU_FOREACH(i) { pc = pcpu_find(i); KASSERT(pc != NULL, ("no pcpu data for %d", i)); if (cpu_unordered) { if (idx-- == 0) { /* * If pc_acpi_id doesn't match the ACPI ID from the * ASL, prefer the MADT-derived value. */ if (pc->pc_acpi_id != *acpi_id) *acpi_id = pc->pc_acpi_id; *cpu_id = pc->pc_cpuid; return (0); } } else { if (pc->pc_acpi_id == *acpi_id) { if (bootverbose) device_printf(dev, "Processor %s (ACPI ID %u) -> APIC ID %d\n", acpi_name(acpi_get_handle(dev)), *acpi_id, pc->pc_cpuid); *cpu_id = pc->pc_cpuid; return (0); } } } if (bootverbose) printf("ACPI: Processor %s (ACPI ID %u) ignored\n", acpi_name(acpi_get_handle(dev)), *acpi_id); return (ESRCH); } static struct resource_list * acpi_cpu_get_rlist(device_t dev, device_t child) { struct acpi_cpu_device *ad; ad = device_get_ivars(child); if (ad == NULL) return (NULL); return (&ad->ad_rl); } static device_t acpi_cpu_add_child(device_t dev, u_int order, const char *name, int unit) { struct acpi_cpu_device *ad; device_t child; if ((ad = malloc(sizeof(*ad), M_TEMP, M_NOWAIT | M_ZERO)) == NULL) return (NULL); resource_list_init(&ad->ad_rl); child = device_add_child_ordered(dev, order, name, unit); if (child != NULL) device_set_ivars(child, ad); else free(ad, M_TEMP); return (child); } static int acpi_cpu_read_ivar(device_t dev, device_t child, int index, uintptr_t *result) { struct acpi_cpu_softc *sc; sc = device_get_softc(dev); switch (index) { case ACPI_IVAR_HANDLE: *result = (uintptr_t)sc->cpu_handle; break; case CPU_IVAR_PCPU: *result = (uintptr_t)sc->cpu_pcpu; break; #if defined(__amd64__) || defined(__i386__) case CPU_IVAR_NOMINAL_MHZ: if (tsc_is_invariant) { *result = (uintptr_t)(atomic_load_acq_64(&tsc_freq) / 1000000); break; } /* FALLTHROUGH */ #endif default: return (ENOENT); } return (0); } static int acpi_cpu_shutdown(device_t dev) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* Allow children to shutdown first. */ bus_generic_shutdown(dev); /* * Disable any entry to the idle function. */ disable_idle(device_get_softc(dev)); /* * CPU devices are not truly detached and remain referenced, * so their resources are not freed. */ return_VALUE (0); } static void acpi_cpu_cx_probe(struct acpi_cpu_softc *sc) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* Use initial sleep value of 1 sec. to start with lowest idle state. */ sc->cpu_prev_sleep = 1000000; sc->cpu_cx_lowest = 0; sc->cpu_cx_lowest_lim = 0; /* * Check for the ACPI 2.0 _CST sleep states object. If we can't find * any, we'll revert to generic FADT/P_BLK Cx control method which will * be handled by acpi_cpu_startup. We need to defer to after having * probed all the cpus in the system before probing for generic Cx * states as we may already have found cpus with valid _CST packages */ if (!cpu_cx_generic && acpi_cpu_cx_cst(sc) != 0) { /* * We were unable to find a _CST package for this cpu or there * was an error parsing it. Switch back to generic mode. */ cpu_cx_generic = TRUE; if (bootverbose) device_printf(sc->cpu_dev, "switching to generic Cx mode\n"); } /* * TODO: _CSD Package should be checked here. */ } static void acpi_cpu_generic_cx_probe(struct acpi_cpu_softc *sc) { ACPI_GENERIC_ADDRESS gas; struct acpi_cx *cx_ptr; sc->cpu_cx_count = 0; cx_ptr = sc->cpu_cx_states; /* Use initial sleep value of 1 sec. to start with lowest idle state. */ sc->cpu_prev_sleep = 1000000; /* C1 has been required since just after ACPI 1.0 */ cx_ptr->type = ACPI_STATE_C1; cx_ptr->trans_lat = 0; cx_ptr++; sc->cpu_non_c2 = sc->cpu_cx_count; sc->cpu_non_c3 = sc->cpu_cx_count; sc->cpu_cx_count++; /* * The spec says P_BLK must be 6 bytes long. However, some systems * use it to indicate a fractional set of features present so we * take 5 as C2. Some may also have a value of 7 to indicate * another C3 but most use _CST for this (as required) and having * "only" C1-C3 is not a hardship. */ if (sc->cpu_p_blk_len < 5) return; /* Validate and allocate resources for C2 (P_LVL2). */ gas.SpaceId = ACPI_ADR_SPACE_SYSTEM_IO; gas.BitWidth = 8; if (AcpiGbl_FADT.C2Latency <= 100) { gas.Address = sc->cpu_p_blk + 4; cx_ptr->res_rid = 0; acpi_bus_alloc_gas(sc->cpu_dev, &cx_ptr->res_type, &cx_ptr->res_rid, &gas, &cx_ptr->p_lvlx, RF_SHAREABLE); if (cx_ptr->p_lvlx != NULL) { cx_ptr->type = ACPI_STATE_C2; cx_ptr->trans_lat = AcpiGbl_FADT.C2Latency; cx_ptr++; sc->cpu_non_c3 = sc->cpu_cx_count; sc->cpu_cx_count++; } } if (sc->cpu_p_blk_len < 6) return; /* Validate and allocate resources for C3 (P_LVL3). */ if (AcpiGbl_FADT.C3Latency <= 1000 && !(cpu_quirks & CPU_QUIRK_NO_C3)) { gas.Address = sc->cpu_p_blk + 5; cx_ptr->res_rid = 1; acpi_bus_alloc_gas(sc->cpu_dev, &cx_ptr->res_type, &cx_ptr->res_rid, &gas, &cx_ptr->p_lvlx, RF_SHAREABLE); if (cx_ptr->p_lvlx != NULL) { cx_ptr->type = ACPI_STATE_C3; cx_ptr->trans_lat = AcpiGbl_FADT.C3Latency; cx_ptr++; sc->cpu_cx_count++; } } } #if defined(__i386__) || defined(__amd64__) static void acpi_cpu_cx_cst_mwait(struct acpi_cx *cx_ptr, uint64_t address, int accsize) { cx_ptr->do_mwait = true; cx_ptr->mwait_hint = address & 0xffffffff; cx_ptr->mwait_hw_coord = (accsize & CST_FFH_MWAIT_HW_COORD) != 0; cx_ptr->mwait_bm_avoidance = (accsize & CST_FFH_MWAIT_BM_AVOID) != 0; } #endif static void acpi_cpu_cx_cst_free_plvlx(device_t cpu_dev, struct acpi_cx *cx_ptr) { if (cx_ptr->p_lvlx == NULL) return; bus_release_resource(cpu_dev, cx_ptr->res_type, cx_ptr->res_rid, cx_ptr->p_lvlx); cx_ptr->p_lvlx = NULL; } /* * Parse a _CST package and set up its Cx states. Since the _CST object * can change dynamically, our notify handler may call this function * to clean up and probe the new _CST package. */ static int acpi_cpu_cx_cst(struct acpi_cpu_softc *sc) { struct acpi_cx *cx_ptr; ACPI_STATUS status; ACPI_BUFFER buf; ACPI_OBJECT *top; ACPI_OBJECT *pkg; uint32_t count; int i; #if defined(__i386__) || defined(__amd64__) uint64_t address; int vendor, class, accsize; #endif ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; status = AcpiEvaluateObject(sc->cpu_handle, "_CST", NULL, &buf); if (ACPI_FAILURE(status)) return (ENXIO); /* _CST is a package with a count and at least one Cx package. */ top = (ACPI_OBJECT *)buf.Pointer; if (!ACPI_PKG_VALID(top, 2) || acpi_PkgInt32(top, 0, &count) != 0) { device_printf(sc->cpu_dev, "invalid _CST package\n"); AcpiOsFree(buf.Pointer); return (ENXIO); } if (count != top->Package.Count - 1) { device_printf(sc->cpu_dev, "invalid _CST state count (%d != %d)\n", count, top->Package.Count - 1); count = top->Package.Count - 1; } if (count > MAX_CX_STATES) { device_printf(sc->cpu_dev, "_CST has too many states (%d)\n", count); count = MAX_CX_STATES; } sc->cpu_non_c2 = 0; sc->cpu_non_c3 = 0; sc->cpu_cx_count = 0; cx_ptr = sc->cpu_cx_states; /* * C1 has been required since just after ACPI 1.0. * Reserve the first slot for it. */ cx_ptr->type = ACPI_STATE_C0; cx_ptr++; sc->cpu_cx_count++; /* Set up all valid states. */ for (i = 0; i < count; i++) { pkg = &top->Package.Elements[i + 1]; if (!ACPI_PKG_VALID(pkg, 4) || acpi_PkgInt32(pkg, 1, &cx_ptr->type) != 0 || acpi_PkgInt32(pkg, 2, &cx_ptr->trans_lat) != 0 || acpi_PkgInt32(pkg, 3, &cx_ptr->power) != 0) { device_printf(sc->cpu_dev, "skipping invalid Cx state package\n"); continue; } /* Validate the state to see if we should use it. */ switch (cx_ptr->type) { case ACPI_STATE_C1: acpi_cpu_cx_cst_free_plvlx(sc->cpu_dev, cx_ptr); #if defined(__i386__) || defined(__amd64__) if (acpi_PkgFFH_IntelCpu(pkg, 0, &vendor, &class, &address, &accsize) == 0 && vendor == CST_FFH_VENDOR_INTEL) { if (class == CST_FFH_INTEL_CL_C1IO) { /* C1 I/O then Halt */ cx_ptr->res_rid = sc->cpu_cx_count; bus_set_resource(sc->cpu_dev, SYS_RES_IOPORT, cx_ptr->res_rid, address, 1); cx_ptr->p_lvlx = bus_alloc_resource_any(sc->cpu_dev, SYS_RES_IOPORT, &cx_ptr->res_rid, RF_ACTIVE | RF_SHAREABLE); if (cx_ptr->p_lvlx == NULL) { bus_delete_resource(sc->cpu_dev, SYS_RES_IOPORT, cx_ptr->res_rid); device_printf(sc->cpu_dev, "C1 I/O failed to allocate port %d, " "degrading to C1 Halt", (int)address); } } else if (class == CST_FFH_INTEL_CL_MWAIT) { acpi_cpu_cx_cst_mwait(cx_ptr, address, accsize); } } #endif if (sc->cpu_cx_states[0].type == ACPI_STATE_C0) { /* This is the first C1 state. Use the reserved slot. */ sc->cpu_cx_states[0] = *cx_ptr; } else { sc->cpu_non_c2 = sc->cpu_cx_count; sc->cpu_non_c3 = sc->cpu_cx_count; cx_ptr++; sc->cpu_cx_count++; } continue; case ACPI_STATE_C2: sc->cpu_non_c3 = sc->cpu_cx_count; break; case ACPI_STATE_C3: default: if ((cpu_quirks & CPU_QUIRK_NO_C3) != 0) { ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu%d: C3[%d] not available.\n", device_get_unit(sc->cpu_dev), i)); continue; } break; } /* Free up any previous register. */ acpi_cpu_cx_cst_free_plvlx(sc->cpu_dev, cx_ptr); /* Allocate the control register for C2 or C3. */ #if defined(__i386__) || defined(__amd64__) if (acpi_PkgFFH_IntelCpu(pkg, 0, &vendor, &class, &address, &accsize) == 0 && vendor == CST_FFH_VENDOR_INTEL && class == CST_FFH_INTEL_CL_MWAIT) { /* Native C State Instruction use (mwait) */ acpi_cpu_cx_cst_mwait(cx_ptr, address, accsize); ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu%d: Got C%d/mwait - %d latency\n", device_get_unit(sc->cpu_dev), cx_ptr->type, cx_ptr->trans_lat)); cx_ptr++; sc->cpu_cx_count++; } else #endif { cx_ptr->res_rid = sc->cpu_cx_count; acpi_PkgGas(sc->cpu_dev, pkg, 0, &cx_ptr->res_type, &cx_ptr->res_rid, &cx_ptr->p_lvlx, RF_SHAREABLE); if (cx_ptr->p_lvlx) { ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu%d: Got C%d - %d latency\n", device_get_unit(sc->cpu_dev), cx_ptr->type, cx_ptr->trans_lat)); cx_ptr++; sc->cpu_cx_count++; } } } AcpiOsFree(buf.Pointer); /* If C1 state was not found, we need one now. */ cx_ptr = sc->cpu_cx_states; if (cx_ptr->type == ACPI_STATE_C0) { cx_ptr->type = ACPI_STATE_C1; cx_ptr->trans_lat = 0; } return (0); } /* * Call this *after* all CPUs have been attached. */ static void acpi_cpu_startup(void *arg) { struct acpi_cpu_softc *sc; int i; /* Get set of CPU devices */ devclass_get_devices(acpi_cpu_devclass, &cpu_devices, &cpu_ndevices); /* * Setup any quirks that might necessary now that we have probed * all the CPUs */ acpi_cpu_quirks(); if (cpu_cx_generic) { /* * We are using generic Cx mode, probe for available Cx states * for all processors. */ for (i = 0; i < cpu_ndevices; i++) { sc = device_get_softc(cpu_devices[i]); acpi_cpu_generic_cx_probe(sc); } } else { /* * We are using _CST mode, remove C3 state if necessary. * As we now know for sure that we will be using _CST mode * install our notify handler. */ for (i = 0; i < cpu_ndevices; i++) { sc = device_get_softc(cpu_devices[i]); if (cpu_quirks & CPU_QUIRK_NO_C3) { sc->cpu_cx_count = min(sc->cpu_cx_count, sc->cpu_non_c3 + 1); } AcpiInstallNotifyHandler(sc->cpu_handle, ACPI_DEVICE_NOTIFY, acpi_cpu_notify, sc); } } /* Perform Cx final initialization. */ for (i = 0; i < cpu_ndevices; i++) { sc = device_get_softc(cpu_devices[i]); acpi_cpu_startup_cx(sc); } /* Add a sysctl handler to handle global Cx lowest setting */ SYSCTL_ADD_PROC(&cpu_sysctl_ctx, SYSCTL_CHILDREN(cpu_sysctl_tree), OID_AUTO, "cx_lowest", CTLTYPE_STRING | CTLFLAG_RW, NULL, 0, acpi_cpu_global_cx_lowest_sysctl, "A", "Global lowest Cx sleep state to use"); /* Take over idling from cpu_idle_default(). */ cpu_cx_lowest_lim = 0; for (i = 0; i < cpu_ndevices; i++) { sc = device_get_softc(cpu_devices[i]); enable_idle(sc); } #if defined(__i386__) || defined(__amd64__) cpu_idle_hook = acpi_cpu_idle; #endif } static void acpi_cpu_cx_list(struct acpi_cpu_softc *sc) { struct sbuf sb; int i; /* * Set up the list of Cx states */ sbuf_new(&sb, sc->cpu_cx_supported, sizeof(sc->cpu_cx_supported), SBUF_FIXEDLEN); for (i = 0; i < sc->cpu_cx_count; i++) sbuf_printf(&sb, "C%d/%d/%d ", i + 1, sc->cpu_cx_states[i].type, sc->cpu_cx_states[i].trans_lat); sbuf_trim(&sb); sbuf_finish(&sb); } static void acpi_cpu_startup_cx(struct acpi_cpu_softc *sc) { acpi_cpu_cx_list(sc); SYSCTL_ADD_STRING(&sc->cpu_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->cpu_dev)), OID_AUTO, "cx_supported", CTLFLAG_RD, sc->cpu_cx_supported, 0, "Cx/microsecond values for supported Cx states"); SYSCTL_ADD_PROC(&sc->cpu_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->cpu_dev)), OID_AUTO, "cx_lowest", CTLTYPE_STRING | CTLFLAG_RW, (void *)sc, 0, acpi_cpu_cx_lowest_sysctl, "A", "lowest Cx sleep state to use"); SYSCTL_ADD_PROC(&sc->cpu_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->cpu_dev)), OID_AUTO, "cx_usage", CTLTYPE_STRING | CTLFLAG_RD, (void *)sc, 0, acpi_cpu_usage_sysctl, "A", "percent usage for each Cx state"); SYSCTL_ADD_PROC(&sc->cpu_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->cpu_dev)), OID_AUTO, "cx_usage_counters", CTLTYPE_STRING | CTLFLAG_RD, (void *)sc, 0, acpi_cpu_usage_counters_sysctl, "A", "Cx sleep state counters"); #if defined(__i386__) || defined(__amd64__) SYSCTL_ADD_PROC(&sc->cpu_sysctl_ctx, SYSCTL_CHILDREN(device_get_sysctl_tree(sc->cpu_dev)), OID_AUTO, "cx_method", CTLTYPE_STRING | CTLFLAG_RD, (void *)sc, 0, acpi_cpu_method_sysctl, "A", "Cx entrance methods"); #endif /* Signal platform that we can handle _CST notification. */ if (!cpu_cx_generic && cpu_cst_cnt != 0) { ACPI_LOCK(acpi); AcpiOsWritePort(cpu_smi_cmd, cpu_cst_cnt, 8); ACPI_UNLOCK(acpi); } } #if defined(__i386__) || defined(__amd64__) /* * Idle the CPU in the lowest state possible. This function is called with * interrupts disabled. Note that once it re-enables interrupts, a task * switch can occur so do not access shared data (i.e. the softc) after * interrupts are re-enabled. */ static void acpi_cpu_idle(sbintime_t sbt) { struct acpi_cpu_softc *sc; struct acpi_cx *cx_next; uint64_t cputicks; uint32_t start_time, end_time; ACPI_STATUS status; int bm_active, cx_next_idx, i, us; /* * Look up our CPU id to get our softc. If it's NULL, we'll use C1 * since there is no ACPI processor object for this CPU. This occurs * for logical CPUs in the HTT case. */ sc = cpu_softc[PCPU_GET(cpuid)]; if (sc == NULL) { acpi_cpu_c1(); return; } /* If disabled, take the safe path. */ if (is_idle_disabled(sc)) { acpi_cpu_c1(); return; } /* Find the lowest state that has small enough latency. */ us = sc->cpu_prev_sleep; if (sbt >= 0 && us > (sbt >> 12)) us = (sbt >> 12); cx_next_idx = 0; if (cpu_disable_c2_sleep) i = min(sc->cpu_cx_lowest, sc->cpu_non_c2); else if (cpu_disable_c3_sleep) i = min(sc->cpu_cx_lowest, sc->cpu_non_c3); else i = sc->cpu_cx_lowest; for (; i >= 0; i--) { if (sc->cpu_cx_states[i].trans_lat * 3 <= us) { cx_next_idx = i; break; } } /* * Check for bus master activity. If there was activity, clear * the bit and use the lowest non-C3 state. Note that the USB * driver polling for new devices keeps this bit set all the * time if USB is loaded. */ if ((cpu_quirks & CPU_QUIRK_NO_BM_CTRL) == 0 && cx_next_idx > sc->cpu_non_c3) { status = AcpiReadBitRegister(ACPI_BITREG_BUS_MASTER_STATUS, &bm_active); if (ACPI_SUCCESS(status) && bm_active != 0) { AcpiWriteBitRegister(ACPI_BITREG_BUS_MASTER_STATUS, 1); cx_next_idx = sc->cpu_non_c3; } } /* Select the next state and update statistics. */ cx_next = &sc->cpu_cx_states[cx_next_idx]; sc->cpu_cx_stats[cx_next_idx]++; KASSERT(cx_next->type != ACPI_STATE_C0, ("acpi_cpu_idle: C0 sleep")); /* * Execute HLT (or equivalent) and wait for an interrupt. We can't * precisely calculate the time spent in C1 since the place we wake up * is an ISR. Assume we slept no more then half of quantum, unless * we are called inside critical section, delaying context switch. */ if (cx_next->type == ACPI_STATE_C1) { cputicks = cpu_ticks(); if (cx_next->p_lvlx != NULL) { /* C1 I/O then Halt */ CPU_GET_REG(cx_next->p_lvlx, 1); } if (cx_next->do_mwait) acpi_cpu_idle_mwait(cx_next->mwait_hint); else acpi_cpu_c1(); end_time = ((cpu_ticks() - cputicks) << 20) / cpu_tickrate(); if (curthread->td_critnest == 0) end_time = min(end_time, 500000 / hz); /* acpi_cpu_c1() returns with interrupts enabled. */ if (cx_next->do_mwait) ACPI_ENABLE_IRQS(); sc->cpu_prev_sleep = (sc->cpu_prev_sleep * 3 + end_time) / 4; return; } /* * For C3, disable bus master arbitration and enable bus master wake * if BM control is available, otherwise flush the CPU cache. */ if (cx_next->type == ACPI_STATE_C3 || cx_next->mwait_bm_avoidance) { if ((cpu_quirks & CPU_QUIRK_NO_BM_CTRL) == 0) { AcpiWriteBitRegister(ACPI_BITREG_ARB_DISABLE, 1); AcpiWriteBitRegister(ACPI_BITREG_BUS_MASTER_RLD, 1); } else ACPI_FLUSH_CPU_CACHE(); } /* * Read from P_LVLx to enter C2(+), checking time spent asleep. * Use the ACPI timer for measuring sleep time. Since we need to * get the time very close to the CPU start/stop clock logic, this * is the only reliable time source. */ if (cx_next->type == ACPI_STATE_C3) { AcpiGetTimer(&start_time); cputicks = 0; } else { start_time = 0; cputicks = cpu_ticks(); } if (cx_next->do_mwait) acpi_cpu_idle_mwait(cx_next->mwait_hint); else CPU_GET_REG(cx_next->p_lvlx, 1); /* * Read the end time twice. Since it may take an arbitrary time * to enter the idle state, the first read may be executed before * the processor has stopped. Doing it again provides enough * margin that we are certain to have a correct value. */ AcpiGetTimer(&end_time); if (cx_next->type == ACPI_STATE_C3) { AcpiGetTimer(&end_time); AcpiGetTimerDuration(start_time, end_time, &end_time); } else end_time = ((cpu_ticks() - cputicks) << 20) / cpu_tickrate(); /* Enable bus master arbitration and disable bus master wakeup. */ if ((cx_next->type == ACPI_STATE_C3 || cx_next->mwait_bm_avoidance) && (cpu_quirks & CPU_QUIRK_NO_BM_CTRL) == 0) { AcpiWriteBitRegister(ACPI_BITREG_ARB_DISABLE, 0); AcpiWriteBitRegister(ACPI_BITREG_BUS_MASTER_RLD, 0); } ACPI_ENABLE_IRQS(); sc->cpu_prev_sleep = (sc->cpu_prev_sleep * 3 + PM_USEC(end_time)) / 4; } #endif /* * Re-evaluate the _CST object when we are notified that it changed. */ static void acpi_cpu_notify(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_cpu_softc *sc = (struct acpi_cpu_softc *)context; if (notify != ACPI_NOTIFY_CX_STATES) return; /* * C-state data for target CPU is going to be in flux while we execute * acpi_cpu_cx_cst, so disable entering acpi_cpu_idle. * Also, it may happen that multiple ACPI taskqueues may concurrently * execute notifications for the same CPU. ACPI_SERIAL is used to * protect against that. */ ACPI_SERIAL_BEGIN(cpu); disable_idle(sc); /* Update the list of Cx states. */ acpi_cpu_cx_cst(sc); acpi_cpu_cx_list(sc); acpi_cpu_set_cx_lowest(sc); enable_idle(sc); ACPI_SERIAL_END(cpu); acpi_UserNotify("PROCESSOR", sc->cpu_handle, notify); } static void acpi_cpu_quirks(void) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* * Bus mastering arbitration control is needed to keep caches coherent * while sleeping in C3. If it's not present but a working flush cache * instruction is present, flush the caches before entering C3 instead. * Otherwise, just disable C3 completely. */ if (AcpiGbl_FADT.Pm2ControlBlock == 0 || AcpiGbl_FADT.Pm2ControlLength == 0) { if ((AcpiGbl_FADT.Flags & ACPI_FADT_WBINVD) && (AcpiGbl_FADT.Flags & ACPI_FADT_WBINVD_FLUSH) == 0) { cpu_quirks |= CPU_QUIRK_NO_BM_CTRL; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu: no BM control, using flush cache method\n")); } else { cpu_quirks |= CPU_QUIRK_NO_C3; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu: no BM control, C3 not available\n")); } } /* * If we are using generic Cx mode, C3 on multiple CPUs requires using * the expensive flush cache instruction. */ if (cpu_cx_generic && mp_ncpus > 1) { cpu_quirks |= CPU_QUIRK_NO_BM_CTRL; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu: SMP, using flush cache mode for C3\n")); } /* Look for various quirks of the PIIX4 part. */ acpi_cpu_quirks_piix4(); } static void acpi_cpu_quirks_piix4(void) { #ifdef __i386__ device_t acpi_dev; uint32_t val; ACPI_STATUS status; acpi_dev = pci_find_device(PCI_VENDOR_INTEL, PCI_DEVICE_82371AB_3); if (acpi_dev != NULL) { switch (pci_get_revid(acpi_dev)) { /* * Disable C3 support for all PIIX4 chipsets. Some of these parts * do not report the BMIDE status to the BM status register and * others have a livelock bug if Type-F DMA is enabled. Linux * works around the BMIDE bug by reading the BM status directly * but we take the simpler approach of disabling C3 for these * parts. * * See erratum #18 ("C3 Power State/BMIDE and Type-F DMA * Livelock") from the January 2002 PIIX4 specification update. * Applies to all PIIX4 models. * * Also, make sure that all interrupts cause a "Stop Break" * event to exit from C2 state. * Also, BRLD_EN_BM (ACPI_BITREG_BUS_MASTER_RLD in ACPI-speak) * should be set to zero, otherwise it causes C2 to short-sleep. * PIIX4 doesn't properly support C3 and bus master activity * need not break out of C2. */ case PCI_REVISION_A_STEP: case PCI_REVISION_B_STEP: case PCI_REVISION_4E: case PCI_REVISION_4M: cpu_quirks |= CPU_QUIRK_NO_C3; ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu: working around PIIX4 bug, disabling C3\n")); val = pci_read_config(acpi_dev, PIIX4_DEVACTB_REG, 4); if ((val & PIIX4_STOP_BREAK_MASK) != PIIX4_STOP_BREAK_MASK) { ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu: PIIX4: enabling IRQs to generate Stop Break\n")); val |= PIIX4_STOP_BREAK_MASK; pci_write_config(acpi_dev, PIIX4_DEVACTB_REG, val, 4); } status = AcpiReadBitRegister(ACPI_BITREG_BUS_MASTER_RLD, &val); if (ACPI_SUCCESS(status) && val != 0) { ACPI_DEBUG_PRINT((ACPI_DB_INFO, "acpi_cpu: PIIX4: reset BRLD_EN_BM\n")); AcpiWriteBitRegister(ACPI_BITREG_BUS_MASTER_RLD, 0); } break; default: break; } } #endif } static int acpi_cpu_usage_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_cpu_softc *sc; struct sbuf sb; char buf[128]; int i; uintmax_t fract, sum, whole; sc = (struct acpi_cpu_softc *) arg1; sum = 0; for (i = 0; i < sc->cpu_cx_count; i++) sum += sc->cpu_cx_stats[i]; sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN); for (i = 0; i < sc->cpu_cx_count; i++) { if (sum > 0) { whole = (uintmax_t)sc->cpu_cx_stats[i] * 100; fract = (whole % sum) * 100; sbuf_printf(&sb, "%u.%02u%% ", (u_int)(whole / sum), (u_int)(fract / sum)); } else sbuf_printf(&sb, "0.00%% "); } sbuf_printf(&sb, "last %dus", sc->cpu_prev_sleep); sbuf_trim(&sb); sbuf_finish(&sb); sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); sbuf_delete(&sb); return (0); } /* * XXX TODO: actually add support to count each entry/exit * from the Cx states. */ static int acpi_cpu_usage_counters_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_cpu_softc *sc; struct sbuf sb; char buf[128]; int i; sc = (struct acpi_cpu_softc *) arg1; /* Print out the raw counters */ sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN); for (i = 0; i < sc->cpu_cx_count; i++) { sbuf_printf(&sb, "%u ", sc->cpu_cx_stats[i]); } sbuf_trim(&sb); sbuf_finish(&sb); sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); sbuf_delete(&sb); return (0); } #if defined(__i386__) || defined(__amd64__) static int acpi_cpu_method_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_cpu_softc *sc; struct acpi_cx *cx; struct sbuf sb; char buf[128]; int i; sc = (struct acpi_cpu_softc *)arg1; sbuf_new(&sb, buf, sizeof(buf), SBUF_FIXEDLEN); for (i = 0; i < sc->cpu_cx_count; i++) { cx = &sc->cpu_cx_states[i]; sbuf_printf(&sb, "C%d/", i + 1); if (cx->do_mwait) { sbuf_cat(&sb, "mwait"); if (cx->mwait_hw_coord) sbuf_cat(&sb, "/hwc"); if (cx->mwait_bm_avoidance) sbuf_cat(&sb, "/bma"); } else if (cx->type == ACPI_STATE_C1) { sbuf_cat(&sb, "hlt"); } else { sbuf_cat(&sb, "io"); } if (cx->type == ACPI_STATE_C1 && cx->p_lvlx != NULL) sbuf_cat(&sb, "/iohlt"); sbuf_putc(&sb, ' '); } sbuf_trim(&sb); sbuf_finish(&sb); sysctl_handle_string(oidp, sbuf_data(&sb), sbuf_len(&sb), req); sbuf_delete(&sb); return (0); } #endif static int acpi_cpu_set_cx_lowest(struct acpi_cpu_softc *sc) { int i; ACPI_SERIAL_ASSERT(cpu); sc->cpu_cx_lowest = min(sc->cpu_cx_lowest_lim, sc->cpu_cx_count - 1); /* If not disabling, cache the new lowest non-C3 state. */ sc->cpu_non_c3 = 0; for (i = sc->cpu_cx_lowest; i >= 0; i--) { if (sc->cpu_cx_states[i].type < ACPI_STATE_C3) { sc->cpu_non_c3 = i; break; } } /* Reset the statistics counters. */ bzero(sc->cpu_cx_stats, sizeof(sc->cpu_cx_stats)); return (0); } static int acpi_cpu_cx_lowest_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_cpu_softc *sc; char state[8]; int val, error; sc = (struct acpi_cpu_softc *) arg1; snprintf(state, sizeof(state), "C%d", sc->cpu_cx_lowest_lim + 1); error = sysctl_handle_string(oidp, state, sizeof(state), req); if (error != 0 || req->newptr == NULL) return (error); if (strlen(state) < 2 || toupper(state[0]) != 'C') return (EINVAL); if (strcasecmp(state, "Cmax") == 0) val = MAX_CX_STATES; else { val = (int) strtol(state + 1, NULL, 10); if (val < 1 || val > MAX_CX_STATES) return (EINVAL); } ACPI_SERIAL_BEGIN(cpu); sc->cpu_cx_lowest_lim = val - 1; acpi_cpu_set_cx_lowest(sc); ACPI_SERIAL_END(cpu); return (0); } static int acpi_cpu_global_cx_lowest_sysctl(SYSCTL_HANDLER_ARGS) { struct acpi_cpu_softc *sc; char state[8]; int val, error, i; snprintf(state, sizeof(state), "C%d", cpu_cx_lowest_lim + 1); error = sysctl_handle_string(oidp, state, sizeof(state), req); if (error != 0 || req->newptr == NULL) return (error); if (strlen(state) < 2 || toupper(state[0]) != 'C') return (EINVAL); if (strcasecmp(state, "Cmax") == 0) val = MAX_CX_STATES; else { val = (int) strtol(state + 1, NULL, 10); if (val < 1 || val > MAX_CX_STATES) return (EINVAL); } /* Update the new lowest useable Cx state for all CPUs. */ ACPI_SERIAL_BEGIN(cpu); cpu_cx_lowest_lim = val - 1; for (i = 0; i < cpu_ndevices; i++) { sc = device_get_softc(cpu_devices[i]); sc->cpu_cx_lowest_lim = cpu_cx_lowest_lim; acpi_cpu_set_cx_lowest(sc); } ACPI_SERIAL_END(cpu); return (0); } Index: head/sys/dev/acpica/acpi_ec.c =================================================================== --- head/sys/dev/acpica/acpi_ec.c (revision 339753) +++ head/sys/dev/acpica/acpi_ec.c (revision 339754) @@ -1,1026 +1,1029 @@ /*- * Copyright (c) 2003-2007 Nate Lawson * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_EC ACPI_MODULE_NAME("EC") /* * EC_COMMAND: * ----------- */ typedef UINT8 EC_COMMAND; #define EC_COMMAND_UNKNOWN ((EC_COMMAND) 0x00) #define EC_COMMAND_READ ((EC_COMMAND) 0x80) #define EC_COMMAND_WRITE ((EC_COMMAND) 0x81) #define EC_COMMAND_BURST_ENABLE ((EC_COMMAND) 0x82) #define EC_COMMAND_BURST_DISABLE ((EC_COMMAND) 0x83) #define EC_COMMAND_QUERY ((EC_COMMAND) 0x84) /* * EC_STATUS: * ---------- * The encoding of the EC status register is illustrated below. * Note that a set bit (1) indicates the property is TRUE * (e.g. if bit 0 is set then the output buffer is full). * +-+-+-+-+-+-+-+-+ * |7|6|5|4|3|2|1|0| * +-+-+-+-+-+-+-+-+ * | | | | | | | | * | | | | | | | +- Output Buffer Full? * | | | | | | +--- Input Buffer Full? * | | | | | +----- * | | | | +------- Data Register is Command Byte? * | | | +--------- Burst Mode Enabled? * | | +----------- SCI Event? * | +------------- SMI Event? * +--------------- * */ typedef UINT8 EC_STATUS; #define EC_FLAG_OUTPUT_BUFFER ((EC_STATUS) 0x01) #define EC_FLAG_INPUT_BUFFER ((EC_STATUS) 0x02) #define EC_FLAG_DATA_IS_CMD ((EC_STATUS) 0x08) #define EC_FLAG_BURST_MODE ((EC_STATUS) 0x10) /* * EC_EVENT: * --------- */ typedef UINT8 EC_EVENT; #define EC_EVENT_UNKNOWN ((EC_EVENT) 0x00) #define EC_EVENT_OUTPUT_BUFFER_FULL ((EC_EVENT) 0x01) #define EC_EVENT_INPUT_BUFFER_EMPTY ((EC_EVENT) 0x02) #define EC_EVENT_SCI ((EC_EVENT) 0x20) #define EC_EVENT_SMI ((EC_EVENT) 0x40) /* Data byte returned after burst enable indicating it was successful. */ #define EC_BURST_ACK 0x90 /* * Register access primitives */ #define EC_GET_DATA(sc) \ bus_space_read_1((sc)->ec_data_tag, (sc)->ec_data_handle, 0) #define EC_SET_DATA(sc, v) \ bus_space_write_1((sc)->ec_data_tag, (sc)->ec_data_handle, 0, (v)) #define EC_GET_CSR(sc) \ bus_space_read_1((sc)->ec_csr_tag, (sc)->ec_csr_handle, 0) #define EC_SET_CSR(sc, v) \ bus_space_write_1((sc)->ec_csr_tag, (sc)->ec_csr_handle, 0, (v)) /* Additional params to pass from the probe routine */ struct acpi_ec_params { int glk; int gpe_bit; ACPI_HANDLE gpe_handle; int uid; }; /* * Driver softc. */ struct acpi_ec_softc { device_t ec_dev; ACPI_HANDLE ec_handle; int ec_uid; ACPI_HANDLE ec_gpehandle; UINT8 ec_gpebit; int ec_data_rid; struct resource *ec_data_res; bus_space_tag_t ec_data_tag; bus_space_handle_t ec_data_handle; int ec_csr_rid; struct resource *ec_csr_res; bus_space_tag_t ec_csr_tag; bus_space_handle_t ec_csr_handle; int ec_glk; int ec_glkhandle; int ec_burstactive; int ec_sci_pend; volatile u_int ec_gencount; int ec_suspending; }; /* * XXX njl * I couldn't find it in the spec but other implementations also use a * value of 1 ms for the time to acquire global lock. */ #define EC_LOCK_TIMEOUT 1000 /* Default delay in microseconds between each run of the status polling loop. */ #define EC_POLL_DELAY 50 /* Total time in ms spent waiting for a response from EC. */ #define EC_TIMEOUT 750 #define EVENT_READY(event, status) \ (((event) == EC_EVENT_OUTPUT_BUFFER_FULL && \ ((status) & EC_FLAG_OUTPUT_BUFFER) != 0) || \ ((event) == EC_EVENT_INPUT_BUFFER_EMPTY && \ ((status) & EC_FLAG_INPUT_BUFFER) == 0)) ACPI_SERIAL_DECL(ec, "ACPI embedded controller"); static SYSCTL_NODE(_debug_acpi, OID_AUTO, ec, CTLFLAG_RD, NULL, "EC debugging"); static int ec_burst_mode; SYSCTL_INT(_debug_acpi_ec, OID_AUTO, burst, CTLFLAG_RWTUN, &ec_burst_mode, 0, "Enable use of burst mode (faster for nearly all systems)"); static int ec_polled_mode; SYSCTL_INT(_debug_acpi_ec, OID_AUTO, polled, CTLFLAG_RWTUN, &ec_polled_mode, 0, "Force use of polled mode (only if interrupt mode doesn't work)"); static int ec_timeout = EC_TIMEOUT; SYSCTL_INT(_debug_acpi_ec, OID_AUTO, timeout, CTLFLAG_RWTUN, &ec_timeout, EC_TIMEOUT, "Total time spent waiting for a response (poll+sleep)"); static ACPI_STATUS EcLock(struct acpi_ec_softc *sc) { ACPI_STATUS status; /* If _GLK is non-zero, acquire the global lock. */ status = AE_OK; if (sc->ec_glk) { status = AcpiAcquireGlobalLock(EC_LOCK_TIMEOUT, &sc->ec_glkhandle); if (ACPI_FAILURE(status)) return (status); } ACPI_SERIAL_BEGIN(ec); return (status); } static void EcUnlock(struct acpi_ec_softc *sc) { ACPI_SERIAL_END(ec); if (sc->ec_glk) AcpiReleaseGlobalLock(sc->ec_glkhandle); } static UINT32 EcGpeHandler(ACPI_HANDLE, UINT32, void *); static ACPI_STATUS EcSpaceSetup(ACPI_HANDLE Region, UINT32 Function, void *Context, void **return_Context); static ACPI_STATUS EcSpaceHandler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address, UINT32 Width, UINT64 *Value, void *Context, void *RegionContext); static ACPI_STATUS EcWaitEvent(struct acpi_ec_softc *sc, EC_EVENT Event, u_int gen_count); static ACPI_STATUS EcCommand(struct acpi_ec_softc *sc, EC_COMMAND cmd); static ACPI_STATUS EcRead(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data); static ACPI_STATUS EcWrite(struct acpi_ec_softc *sc, UINT8 Address, UINT8 Data); static int acpi_ec_probe(device_t dev); static int acpi_ec_attach(device_t dev); static int acpi_ec_suspend(device_t dev); static int acpi_ec_resume(device_t dev); static int acpi_ec_shutdown(device_t dev); static int acpi_ec_read_method(device_t dev, u_int addr, UINT64 *val, int width); static int acpi_ec_write_method(device_t dev, u_int addr, UINT64 val, int width); static device_method_t acpi_ec_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_ec_probe), DEVMETHOD(device_attach, acpi_ec_attach), DEVMETHOD(device_suspend, acpi_ec_suspend), DEVMETHOD(device_resume, acpi_ec_resume), DEVMETHOD(device_shutdown, acpi_ec_shutdown), /* Embedded controller interface */ DEVMETHOD(acpi_ec_read, acpi_ec_read_method), DEVMETHOD(acpi_ec_write, acpi_ec_write_method), DEVMETHOD_END }; static driver_t acpi_ec_driver = { "acpi_ec", acpi_ec_methods, sizeof(struct acpi_ec_softc), }; static devclass_t acpi_ec_devclass; DRIVER_MODULE(acpi_ec, acpi, acpi_ec_driver, acpi_ec_devclass, 0, 0); MODULE_DEPEND(acpi_ec, acpi, 1, 1, 1); /* * Look for an ECDT and if we find one, set up default GPE and * space handlers to catch attempts to access EC space before * we have a real driver instance in place. * * TODO: Some old Gateway laptops need us to fake up an ECDT or * otherwise attach early so that _REG methods can run. */ void acpi_ec_ecdt_probe(device_t parent) { ACPI_TABLE_ECDT *ecdt; ACPI_STATUS status; device_t child; ACPI_HANDLE h; struct acpi_ec_params *params; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* Find and validate the ECDT. */ status = AcpiGetTable(ACPI_SIG_ECDT, 1, (ACPI_TABLE_HEADER **)&ecdt); if (ACPI_FAILURE(status) || ecdt->Control.BitWidth != 8 || ecdt->Data.BitWidth != 8) { return; } /* Create the child device with the given unit number. */ child = BUS_ADD_CHILD(parent, 3, "acpi_ec", ecdt->Uid); if (child == NULL) { printf("%s: can't add child\n", __func__); return; } /* Find and save the ACPI handle for this device. */ status = AcpiGetHandle(NULL, ecdt->Id, &h); if (ACPI_FAILURE(status)) { device_delete_child(parent, child); printf("%s: can't get handle\n", __func__); return; } acpi_set_handle(child, h); /* Set the data and CSR register addresses. */ bus_set_resource(child, SYS_RES_IOPORT, 0, ecdt->Data.Address, /*count*/1); bus_set_resource(child, SYS_RES_IOPORT, 1, ecdt->Control.Address, /*count*/1); /* * Store values for the probe/attach routines to use. Store the * ECDT GPE bit and set the global lock flag according to _GLK. * Note that it is not perfectly correct to be evaluating a method * before initializing devices, but in practice this function * should be safe to call at this point. */ params = malloc(sizeof(struct acpi_ec_params), M_TEMP, M_WAITOK | M_ZERO); params->gpe_handle = NULL; params->gpe_bit = ecdt->Gpe; params->uid = ecdt->Uid; acpi_GetInteger(h, "_GLK", ¶ms->glk); acpi_set_private(child, params); /* Finish the attach process. */ if (device_probe_and_attach(child) != 0) device_delete_child(parent, child); } static int acpi_ec_probe(device_t dev) { ACPI_BUFFER buf; ACPI_HANDLE h; ACPI_OBJECT *obj; ACPI_STATUS status; device_t peer; char desc[64]; int ecdt; int ret; struct acpi_ec_params *params; static char *ec_ids[] = { "PNP0C09", NULL }; /* Check that this is a device and that EC is not disabled. */ if (acpi_get_type(dev) != ACPI_TYPE_DEVICE || acpi_disabled("ec")) return (ENXIO); /* * If probed via ECDT, set description and continue. Otherwise, * we can access the namespace and make sure this is not a * duplicate probe. */ ret = ENXIO; ecdt = 0; buf.Pointer = NULL; buf.Length = ACPI_ALLOCATE_BUFFER; params = acpi_get_private(dev); if (params != NULL) { ecdt = 1; ret = 0; - } else if (ACPI_ID_PROBE(device_get_parent(dev), dev, ec_ids)) { + } else { + ret = ACPI_ID_PROBE(device_get_parent(dev), dev, ec_ids, NULL); + if (ret > 0) + goto out; params = malloc(sizeof(struct acpi_ec_params), M_TEMP, M_WAITOK | M_ZERO); h = acpi_get_handle(dev); /* * Read the unit ID to check for duplicate attach and the * global lock value to see if we should acquire it when * accessing the EC. */ status = acpi_GetInteger(h, "_UID", ¶ms->uid); if (ACPI_FAILURE(status)) params->uid = 0; status = acpi_GetInteger(h, "_GLK", ¶ms->glk); if (ACPI_FAILURE(status)) params->glk = 0; /* * Evaluate the _GPE method to find the GPE bit used by the EC to * signal status (SCI). If it's a package, it contains a reference * and GPE bit, similar to _PRW. */ status = AcpiEvaluateObject(h, "_GPE", NULL, &buf); if (ACPI_FAILURE(status)) { device_printf(dev, "can't evaluate _GPE - %s\n", AcpiFormatException(status)); goto out; } obj = (ACPI_OBJECT *)buf.Pointer; if (obj == NULL) goto out; switch (obj->Type) { case ACPI_TYPE_INTEGER: params->gpe_handle = NULL; params->gpe_bit = obj->Integer.Value; break; case ACPI_TYPE_PACKAGE: if (!ACPI_PKG_VALID(obj, 2)) goto out; params->gpe_handle = acpi_GetReference(NULL, &obj->Package.Elements[0]); if (params->gpe_handle == NULL || acpi_PkgInt32(obj, 1, ¶ms->gpe_bit) != 0) goto out; break; default: device_printf(dev, "_GPE has invalid type %d\n", obj->Type); goto out; } /* Store the values we got from the namespace for attach. */ acpi_set_private(dev, params); /* * Check for a duplicate probe. This can happen when a probe * via ECDT succeeded already. If this is a duplicate, disable * this device. */ peer = devclass_get_device(acpi_ec_devclass, params->uid); - if (peer == NULL || !device_is_alive(peer)) - ret = 0; - else + if (peer != NULL && device_is_alive(peer)){ + ret = ENXIO; device_disable(dev); + } } out: - if (ret == 0) { + if (ret <= 0) { snprintf(desc, sizeof(desc), "Embedded Controller: GPE %#x%s%s", params->gpe_bit, (params->glk) ? ", GLK" : "", ecdt ? ", ECDT" : ""); device_set_desc_copy(dev, desc); } else free(params, M_TEMP); if (buf.Pointer) AcpiOsFree(buf.Pointer); return (ret); } static int acpi_ec_attach(device_t dev) { struct acpi_ec_softc *sc; struct acpi_ec_params *params; ACPI_STATUS Status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* Fetch/initialize softc (assumes softc is pre-zeroed). */ sc = device_get_softc(dev); params = acpi_get_private(dev); sc->ec_dev = dev; sc->ec_handle = acpi_get_handle(dev); /* Retrieve previously probed values via device ivars. */ sc->ec_glk = params->glk; sc->ec_gpebit = params->gpe_bit; sc->ec_gpehandle = params->gpe_handle; sc->ec_uid = params->uid; sc->ec_suspending = FALSE; acpi_set_private(dev, NULL); free(params, M_TEMP); /* Attach bus resources for data and command/status ports. */ sc->ec_data_rid = 0; sc->ec_data_res = bus_alloc_resource_any(sc->ec_dev, SYS_RES_IOPORT, &sc->ec_data_rid, RF_ACTIVE); if (sc->ec_data_res == NULL) { device_printf(dev, "can't allocate data port\n"); goto error; } sc->ec_data_tag = rman_get_bustag(sc->ec_data_res); sc->ec_data_handle = rman_get_bushandle(sc->ec_data_res); sc->ec_csr_rid = 1; sc->ec_csr_res = bus_alloc_resource_any(sc->ec_dev, SYS_RES_IOPORT, &sc->ec_csr_rid, RF_ACTIVE); if (sc->ec_csr_res == NULL) { device_printf(dev, "can't allocate command/status port\n"); goto error; } sc->ec_csr_tag = rman_get_bustag(sc->ec_csr_res); sc->ec_csr_handle = rman_get_bushandle(sc->ec_csr_res); /* * Install a handler for this EC's GPE bit. We want edge-triggered * behavior. */ ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "attaching GPE handler\n")); Status = AcpiInstallGpeHandler(sc->ec_gpehandle, sc->ec_gpebit, ACPI_GPE_EDGE_TRIGGERED, EcGpeHandler, sc); if (ACPI_FAILURE(Status)) { device_printf(dev, "can't install GPE handler for %s - %s\n", acpi_name(sc->ec_handle), AcpiFormatException(Status)); goto error; } /* * Install address space handler */ ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "attaching address space handler\n")); Status = AcpiInstallAddressSpaceHandler(sc->ec_handle, ACPI_ADR_SPACE_EC, &EcSpaceHandler, &EcSpaceSetup, sc); if (ACPI_FAILURE(Status)) { device_printf(dev, "can't install address space handler for %s - %s\n", acpi_name(sc->ec_handle), AcpiFormatException(Status)); goto error; } /* Enable runtime GPEs for the handler. */ Status = AcpiEnableGpe(sc->ec_gpehandle, sc->ec_gpebit); if (ACPI_FAILURE(Status)) { device_printf(dev, "AcpiEnableGpe failed: %s\n", AcpiFormatException(Status)); goto error; } ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "acpi_ec_attach complete\n")); return (0); error: AcpiRemoveGpeHandler(sc->ec_gpehandle, sc->ec_gpebit, EcGpeHandler); AcpiRemoveAddressSpaceHandler(sc->ec_handle, ACPI_ADR_SPACE_EC, EcSpaceHandler); if (sc->ec_csr_res) bus_release_resource(sc->ec_dev, SYS_RES_IOPORT, sc->ec_csr_rid, sc->ec_csr_res); if (sc->ec_data_res) bus_release_resource(sc->ec_dev, SYS_RES_IOPORT, sc->ec_data_rid, sc->ec_data_res); return (ENXIO); } static int acpi_ec_suspend(device_t dev) { struct acpi_ec_softc *sc; sc = device_get_softc(dev); sc->ec_suspending = TRUE; return (0); } static int acpi_ec_resume(device_t dev) { struct acpi_ec_softc *sc; sc = device_get_softc(dev); sc->ec_suspending = FALSE; return (0); } static int acpi_ec_shutdown(device_t dev) { struct acpi_ec_softc *sc; /* Disable the GPE so we don't get EC events during shutdown. */ sc = device_get_softc(dev); AcpiDisableGpe(sc->ec_gpehandle, sc->ec_gpebit); return (0); } /* Methods to allow other devices (e.g., smbat) to read/write EC space. */ static int acpi_ec_read_method(device_t dev, u_int addr, UINT64 *val, int width) { struct acpi_ec_softc *sc; ACPI_STATUS status; sc = device_get_softc(dev); status = EcSpaceHandler(ACPI_READ, addr, width * 8, val, sc, NULL); if (ACPI_FAILURE(status)) return (ENXIO); return (0); } static int acpi_ec_write_method(device_t dev, u_int addr, UINT64 val, int width) { struct acpi_ec_softc *sc; ACPI_STATUS status; sc = device_get_softc(dev); status = EcSpaceHandler(ACPI_WRITE, addr, width * 8, &val, sc, NULL); if (ACPI_FAILURE(status)) return (ENXIO); return (0); } static ACPI_STATUS EcCheckStatus(struct acpi_ec_softc *sc, const char *msg, EC_EVENT event) { ACPI_STATUS status; EC_STATUS ec_status; status = AE_NO_HARDWARE_RESPONSE; ec_status = EC_GET_CSR(sc); if (sc->ec_burstactive && !(ec_status & EC_FLAG_BURST_MODE)) { CTR1(KTR_ACPI, "ec burst disabled in waitevent (%s)", msg); sc->ec_burstactive = FALSE; } if (EVENT_READY(event, ec_status)) { CTR2(KTR_ACPI, "ec %s wait ready, status %#x", msg, ec_status); status = AE_OK; } return (status); } static void EcGpeQueryHandlerSub(struct acpi_ec_softc *sc) { UINT8 Data; ACPI_STATUS Status; int retry; char qxx[5]; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* Serialize user access with EcSpaceHandler(). */ Status = EcLock(sc); if (ACPI_FAILURE(Status)) { device_printf(sc->ec_dev, "GpeQuery lock error: %s\n", AcpiFormatException(Status)); return; } /* * Send a query command to the EC to find out which _Qxx call it * wants to make. This command clears the SCI bit and also the * interrupt source since we are edge-triggered. To prevent the GPE * that may arise from running the query from causing another query * to be queued, we clear the pending flag only after running it. */ for (retry = 0; retry < 2; retry++) { Status = EcCommand(sc, EC_COMMAND_QUERY); if (ACPI_SUCCESS(Status)) break; if (ACPI_FAILURE(EcCheckStatus(sc, "retr_check", EC_EVENT_INPUT_BUFFER_EMPTY))) break; } if (ACPI_FAILURE(Status)) { EcUnlock(sc); device_printf(sc->ec_dev, "GPE query failed: %s\n", AcpiFormatException(Status)); return; } Data = EC_GET_DATA(sc); /* * We have to unlock before running the _Qxx method below since that * method may attempt to read/write from EC address space, causing * recursive acquisition of the lock. */ EcUnlock(sc); /* Ignore the value for "no outstanding event". (13.3.5) */ CTR2(KTR_ACPI, "ec query ok,%s running _Q%02X", Data ? "" : " not", Data); if (Data == 0) return; /* Evaluate _Qxx to respond to the controller. */ snprintf(qxx, sizeof(qxx), "_Q%02X", Data); AcpiUtStrupr(qxx); Status = AcpiEvaluateObject(sc->ec_handle, qxx, NULL, NULL); if (ACPI_FAILURE(Status) && Status != AE_NOT_FOUND) { device_printf(sc->ec_dev, "evaluation of query method %s failed: %s\n", qxx, AcpiFormatException(Status)); } } static void EcGpeQueryHandler(void *Context) { struct acpi_ec_softc *sc = (struct acpi_ec_softc *)Context; int pending; KASSERT(Context != NULL, ("EcGpeQueryHandler called with NULL")); do { /* Read the current pending count */ pending = atomic_load_acq_int(&sc->ec_sci_pend); /* Call GPE handler function */ EcGpeQueryHandlerSub(sc); /* * Try to reset the pending count to zero. If this fails we * know another GPE event has occurred while handling the * current GPE event and need to loop. */ } while (!atomic_cmpset_int(&sc->ec_sci_pend, pending, 0)); } /* * The GPE handler is called when IBE/OBF or SCI events occur. We are * called from an unknown lock context. */ static UINT32 EcGpeHandler(ACPI_HANDLE GpeDevice, UINT32 GpeNumber, void *Context) { struct acpi_ec_softc *sc = Context; ACPI_STATUS Status; EC_STATUS EcStatus; KASSERT(Context != NULL, ("EcGpeHandler called with NULL")); CTR0(KTR_ACPI, "ec gpe handler start"); /* * Notify EcWaitEvent() that the status register is now fresh. If we * didn't do this, it wouldn't be possible to distinguish an old IBE * from a new one, for example when doing a write transaction (writing * address and then data values.) */ atomic_add_int(&sc->ec_gencount, 1); wakeup(sc); /* * If the EC_SCI bit of the status register is set, queue a query handler. * It will run the query and _Qxx method later, under the lock. */ EcStatus = EC_GET_CSR(sc); if ((EcStatus & EC_EVENT_SCI) && atomic_fetchadd_int(&sc->ec_sci_pend, 1) == 0) { CTR0(KTR_ACPI, "ec gpe queueing query handler"); Status = AcpiOsExecute(OSL_GPE_HANDLER, EcGpeQueryHandler, Context); if (ACPI_FAILURE(Status)) { printf("EcGpeHandler: queuing GPE query handler failed\n"); atomic_store_rel_int(&sc->ec_sci_pend, 0); } } return (ACPI_REENABLE_GPE); } static ACPI_STATUS EcSpaceSetup(ACPI_HANDLE Region, UINT32 Function, void *Context, void **RegionContext) { ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); /* * If deactivating a region, always set the output to NULL. Otherwise, * just pass the context through. */ if (Function == ACPI_REGION_DEACTIVATE) *RegionContext = NULL; else *RegionContext = Context; return_ACPI_STATUS (AE_OK); } static ACPI_STATUS EcSpaceHandler(UINT32 Function, ACPI_PHYSICAL_ADDRESS Address, UINT32 Width, UINT64 *Value, void *Context, void *RegionContext) { struct acpi_ec_softc *sc = (struct acpi_ec_softc *)Context; ACPI_PHYSICAL_ADDRESS EcAddr; UINT8 *EcData; ACPI_STATUS Status; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, (UINT32)Address); if (Function != ACPI_READ && Function != ACPI_WRITE) return_ACPI_STATUS (AE_BAD_PARAMETER); if (Width % 8 != 0 || Value == NULL || Context == NULL) return_ACPI_STATUS (AE_BAD_PARAMETER); if (Address + Width / 8 > 256) return_ACPI_STATUS (AE_BAD_ADDRESS); /* * If booting, check if we need to run the query handler. If so, we * we call it directly here since our thread taskq is not active yet. */ if (cold || rebooting || sc->ec_suspending) { if ((EC_GET_CSR(sc) & EC_EVENT_SCI) && atomic_fetchadd_int(&sc->ec_sci_pend, 1) == 0) { CTR0(KTR_ACPI, "ec running gpe handler directly"); EcGpeQueryHandler(sc); } } /* Serialize with EcGpeQueryHandler() at transaction granularity. */ Status = EcLock(sc); if (ACPI_FAILURE(Status)) return_ACPI_STATUS (Status); /* If we can't start burst mode, continue anyway. */ Status = EcCommand(sc, EC_COMMAND_BURST_ENABLE); if (ACPI_SUCCESS(Status)) { if (EC_GET_DATA(sc) == EC_BURST_ACK) { CTR0(KTR_ACPI, "ec burst enabled"); sc->ec_burstactive = TRUE; } } /* Perform the transaction(s), based on Width. */ EcAddr = Address; EcData = (UINT8 *)Value; if (Function == ACPI_READ) *Value = 0; do { switch (Function) { case ACPI_READ: Status = EcRead(sc, EcAddr, EcData); break; case ACPI_WRITE: Status = EcWrite(sc, EcAddr, *EcData); break; } if (ACPI_FAILURE(Status)) break; EcAddr++; EcData++; } while (EcAddr < Address + Width / 8); if (sc->ec_burstactive) { sc->ec_burstactive = FALSE; if (ACPI_SUCCESS(EcCommand(sc, EC_COMMAND_BURST_DISABLE))) CTR0(KTR_ACPI, "ec disabled burst ok"); } EcUnlock(sc); return_ACPI_STATUS (Status); } static ACPI_STATUS EcWaitEvent(struct acpi_ec_softc *sc, EC_EVENT Event, u_int gen_count) { static int no_intr = 0; ACPI_STATUS Status; int count, i, need_poll, slp_ival; ACPI_SERIAL_ASSERT(ec); Status = AE_NO_HARDWARE_RESPONSE; need_poll = cold || rebooting || ec_polled_mode || sc->ec_suspending; /* Wait for event by polling or GPE (interrupt). */ if (need_poll) { count = (ec_timeout * 1000) / EC_POLL_DELAY; if (count == 0) count = 1; DELAY(10); for (i = 0; i < count; i++) { Status = EcCheckStatus(sc, "poll", Event); if (ACPI_SUCCESS(Status)) break; DELAY(EC_POLL_DELAY); } } else { slp_ival = hz / 1000; if (slp_ival != 0) { count = ec_timeout; } else { /* hz has less than 1 ms resolution so scale timeout. */ slp_ival = 1; count = ec_timeout / (1000 / hz); } /* * Wait for the GPE to signal the status changed, checking the * status register each time we get one. It's possible to get a * GPE for an event we're not interested in here (i.e., SCI for * EC query). */ for (i = 0; i < count; i++) { if (gen_count == sc->ec_gencount) tsleep(sc, 0, "ecgpe", slp_ival); /* * Record new generation count. It's possible the GPE was * just to notify us that a query is needed and we need to * wait for a second GPE to signal the completion of the * event we are actually waiting for. */ Status = EcCheckStatus(sc, "sleep", Event); if (ACPI_SUCCESS(Status)) { if (gen_count == sc->ec_gencount) no_intr++; else no_intr = 0; break; } gen_count = sc->ec_gencount; } /* * We finished waiting for the GPE and it never arrived. Try to * read the register once and trust whatever value we got. This is * the best we can do at this point. */ if (ACPI_FAILURE(Status)) Status = EcCheckStatus(sc, "sleep_end", Event); } if (!need_poll && no_intr > 10) { device_printf(sc->ec_dev, "not getting interrupts, switched to polled mode\n"); ec_polled_mode = 1; } if (ACPI_FAILURE(Status)) CTR0(KTR_ACPI, "error: ec wait timed out"); return (Status); } static ACPI_STATUS EcCommand(struct acpi_ec_softc *sc, EC_COMMAND cmd) { ACPI_STATUS status; EC_EVENT event; EC_STATUS ec_status; u_int gen_count; ACPI_SERIAL_ASSERT(ec); /* Don't use burst mode if user disabled it. */ if (!ec_burst_mode && cmd == EC_COMMAND_BURST_ENABLE) return (AE_ERROR); /* Decide what to wait for based on command type. */ switch (cmd) { case EC_COMMAND_READ: case EC_COMMAND_WRITE: case EC_COMMAND_BURST_DISABLE: event = EC_EVENT_INPUT_BUFFER_EMPTY; break; case EC_COMMAND_QUERY: case EC_COMMAND_BURST_ENABLE: event = EC_EVENT_OUTPUT_BUFFER_FULL; break; default: device_printf(sc->ec_dev, "EcCommand: invalid command %#x\n", cmd); return (AE_BAD_PARAMETER); } /* * Ensure empty input buffer before issuing command. * Use generation count of zero to force a quick check. */ status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY, 0); if (ACPI_FAILURE(status)) return (status); /* Run the command and wait for the chosen event. */ CTR1(KTR_ACPI, "ec running command %#x", cmd); gen_count = sc->ec_gencount; EC_SET_CSR(sc, cmd); status = EcWaitEvent(sc, event, gen_count); if (ACPI_SUCCESS(status)) { /* If we succeeded, burst flag should now be present. */ if (cmd == EC_COMMAND_BURST_ENABLE) { ec_status = EC_GET_CSR(sc); if ((ec_status & EC_FLAG_BURST_MODE) == 0) status = AE_ERROR; } } else device_printf(sc->ec_dev, "EcCommand: no response to %#x\n", cmd); return (status); } static ACPI_STATUS EcRead(struct acpi_ec_softc *sc, UINT8 Address, UINT8 *Data) { ACPI_STATUS status; u_int gen_count; int retry; ACPI_SERIAL_ASSERT(ec); CTR1(KTR_ACPI, "ec read from %#x", Address); for (retry = 0; retry < 2; retry++) { status = EcCommand(sc, EC_COMMAND_READ); if (ACPI_FAILURE(status)) return (status); gen_count = sc->ec_gencount; EC_SET_DATA(sc, Address); status = EcWaitEvent(sc, EC_EVENT_OUTPUT_BUFFER_FULL, gen_count); if (ACPI_SUCCESS(status)) { *Data = EC_GET_DATA(sc); return (AE_OK); } if (ACPI_FAILURE(EcCheckStatus(sc, "retr_check", EC_EVENT_INPUT_BUFFER_EMPTY))) break; } device_printf(sc->ec_dev, "EcRead: failed waiting to get data\n"); return (status); } static ACPI_STATUS EcWrite(struct acpi_ec_softc *sc, UINT8 Address, UINT8 Data) { ACPI_STATUS status; u_int gen_count; ACPI_SERIAL_ASSERT(ec); CTR2(KTR_ACPI, "ec write to %#x, data %#x", Address, Data); status = EcCommand(sc, EC_COMMAND_WRITE); if (ACPI_FAILURE(status)) return (status); gen_count = sc->ec_gencount; EC_SET_DATA(sc, Address); status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY, gen_count); if (ACPI_FAILURE(status)) { device_printf(sc->ec_dev, "EcWrite: failed waiting for sent address\n"); return (status); } gen_count = sc->ec_gencount; EC_SET_DATA(sc, Data); status = EcWaitEvent(sc, EC_EVENT_INPUT_BUFFER_EMPTY, gen_count); if (ACPI_FAILURE(status)) { device_printf(sc->ec_dev, "EcWrite: failed waiting for sent data\n"); return (status); } return (AE_OK); } Index: head/sys/dev/acpica/acpi_hpet.c =================================================================== --- head/sys/dev/acpica/acpi_hpet.c (revision 339753) +++ head/sys/dev/acpica/acpi_hpet.c (revision 339754) @@ -1,991 +1,990 @@ /*- * Copyright (c) 2005 Poul-Henning Kamp * Copyright (c) 2010 Alexander Motin * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #if defined(__amd64__) #define DEV_APIC #else #include "opt_apic.h" #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DEV_APIC #include "pcib_if.h" #endif #define HPET_VENDID_AMD 0x4353 #define HPET_VENDID_AMD2 0x1022 #define HPET_VENDID_INTEL 0x8086 #define HPET_VENDID_NVIDIA 0x10de #define HPET_VENDID_SW 0x1166 ACPI_SERIAL_DECL(hpet, "ACPI HPET support"); static devclass_t hpet_devclass; /* ACPI CA debugging */ #define _COMPONENT ACPI_TIMER ACPI_MODULE_NAME("HPET") struct hpet_softc { device_t dev; int mem_rid; int intr_rid; int irq; int useirq; int legacy_route; int per_cpu; uint32_t allowed_irqs; struct resource *mem_res; struct resource *intr_res; void *intr_handle; ACPI_HANDLE handle; uint32_t acpi_uid; uint64_t freq; uint32_t caps; struct timecounter tc; struct hpet_timer { struct eventtimer et; struct hpet_softc *sc; int num; int mode; #define TIMER_STOPPED 0 #define TIMER_PERIODIC 1 #define TIMER_ONESHOT 2 int intr_rid; int irq; int pcpu_cpu; int pcpu_misrouted; int pcpu_master; int pcpu_slaves[MAXCPU]; struct resource *intr_res; void *intr_handle; uint32_t caps; uint32_t vectors; uint32_t div; uint32_t next; char name[8]; } t[32]; int num_timers; struct cdev *pdev; int mmap_allow; int mmap_allow_write; }; static d_open_t hpet_open; static d_mmap_t hpet_mmap; static struct cdevsw hpet_cdevsw = { .d_version = D_VERSION, .d_name = "hpet", .d_open = hpet_open, .d_mmap = hpet_mmap, }; static u_int hpet_get_timecount(struct timecounter *tc); static void hpet_test(struct hpet_softc *sc); static char *hpet_ids[] = { "PNP0103", NULL }; /* Knob to disable acpi_hpet device */ bool acpi_hpet_disabled = false; static u_int hpet_get_timecount(struct timecounter *tc) { struct hpet_softc *sc; sc = tc->tc_priv; return (bus_read_4(sc->mem_res, HPET_MAIN_COUNTER)); } uint32_t hpet_vdso_timehands(struct vdso_timehands *vdso_th, struct timecounter *tc) { struct hpet_softc *sc; sc = tc->tc_priv; vdso_th->th_algo = VDSO_TH_ALGO_X86_HPET; vdso_th->th_x86_shift = 0; vdso_th->th_x86_hpet_idx = device_get_unit(sc->dev); bzero(vdso_th->th_res, sizeof(vdso_th->th_res)); return (sc->mmap_allow != 0); } #ifdef COMPAT_FREEBSD32 uint32_t hpet_vdso_timehands32(struct vdso_timehands32 *vdso_th32, struct timecounter *tc) { struct hpet_softc *sc; sc = tc->tc_priv; vdso_th32->th_algo = VDSO_TH_ALGO_X86_HPET; vdso_th32->th_x86_shift = 0; vdso_th32->th_x86_hpet_idx = device_get_unit(sc->dev); bzero(vdso_th32->th_res, sizeof(vdso_th32->th_res)); return (sc->mmap_allow != 0); } #endif static void hpet_enable(struct hpet_softc *sc) { uint32_t val; val = bus_read_4(sc->mem_res, HPET_CONFIG); if (sc->legacy_route) val |= HPET_CNF_LEG_RT; else val &= ~HPET_CNF_LEG_RT; val |= HPET_CNF_ENABLE; bus_write_4(sc->mem_res, HPET_CONFIG, val); } static void hpet_disable(struct hpet_softc *sc) { uint32_t val; val = bus_read_4(sc->mem_res, HPET_CONFIG); val &= ~HPET_CNF_ENABLE; bus_write_4(sc->mem_res, HPET_CONFIG, val); } static int hpet_start(struct eventtimer *et, sbintime_t first, sbintime_t period) { struct hpet_timer *mt = (struct hpet_timer *)et->et_priv; struct hpet_timer *t; struct hpet_softc *sc = mt->sc; uint32_t fdiv, now; t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; if (period != 0) { t->mode = TIMER_PERIODIC; t->div = (sc->freq * period) >> 32; } else { t->mode = TIMER_ONESHOT; t->div = 0; } if (first != 0) fdiv = (sc->freq * first) >> 32; else fdiv = t->div; if (t->irq < 0) bus_write_4(sc->mem_res, HPET_ISR, 1 << t->num); t->caps |= HPET_TCNF_INT_ENB; now = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); restart: t->next = now + fdiv; if (t->mode == TIMER_PERIODIC && (t->caps & HPET_TCAP_PER_INT)) { t->caps |= HPET_TCNF_TYPE; bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps | HPET_TCNF_VAL_SET); bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), t->next); bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), t->div); } else { t->caps &= ~HPET_TCNF_TYPE; bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps); bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), t->next); } now = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); if ((int32_t)(now - t->next + HPET_MIN_CYCLES) >= 0) { fdiv *= 2; goto restart; } return (0); } static int hpet_stop(struct eventtimer *et) { struct hpet_timer *mt = (struct hpet_timer *)et->et_priv; struct hpet_timer *t; struct hpet_softc *sc = mt->sc; t = (mt->pcpu_master < 0) ? mt : &sc->t[mt->pcpu_slaves[curcpu]]; t->mode = TIMER_STOPPED; t->caps &= ~(HPET_TCNF_INT_ENB | HPET_TCNF_TYPE); bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps); return (0); } static int hpet_intr_single(void *arg) { struct hpet_timer *t = (struct hpet_timer *)arg; struct hpet_timer *mt; struct hpet_softc *sc = t->sc; uint32_t now; if (t->mode == TIMER_STOPPED) return (FILTER_STRAY); /* Check that per-CPU timer interrupt reached right CPU. */ if (t->pcpu_cpu >= 0 && t->pcpu_cpu != curcpu) { if ((++t->pcpu_misrouted) % 32 == 0) { printf("HPET interrupt routed to the wrong CPU" " (timer %d CPU %d -> %d)!\n", t->num, t->pcpu_cpu, curcpu); } /* * Reload timer, hoping that next time may be more lucky * (system will manage proper interrupt binding). */ if ((t->mode == TIMER_PERIODIC && (t->caps & HPET_TCAP_PER_INT) == 0) || t->mode == TIMER_ONESHOT) { t->next = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER) + sc->freq / 8; bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), t->next); } return (FILTER_HANDLED); } if (t->mode == TIMER_PERIODIC && (t->caps & HPET_TCAP_PER_INT) == 0) { t->next += t->div; now = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); if ((int32_t)((now + t->div / 2) - t->next) > 0) t->next = now + t->div / 2; bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), t->next); } else if (t->mode == TIMER_ONESHOT) t->mode = TIMER_STOPPED; mt = (t->pcpu_master < 0) ? t : &sc->t[t->pcpu_master]; if (mt->et.et_active) mt->et.et_event_cb(&mt->et, mt->et.et_arg); return (FILTER_HANDLED); } static int hpet_intr(void *arg) { struct hpet_softc *sc = (struct hpet_softc *)arg; int i; uint32_t val; val = bus_read_4(sc->mem_res, HPET_ISR); if (val) { bus_write_4(sc->mem_res, HPET_ISR, val); val &= sc->useirq; for (i = 0; i < sc->num_timers; i++) { if ((val & (1 << i)) == 0) continue; hpet_intr_single(&sc->t[i]); } return (FILTER_HANDLED); } return (FILTER_STRAY); } uint32_t hpet_get_uid(device_t dev) { struct hpet_softc *sc; sc = device_get_softc(dev); return (sc->acpi_uid); } static ACPI_STATUS hpet_find(ACPI_HANDLE handle, UINT32 level, void *context, void **status) { char **ids; uint32_t id = (uint32_t)(uintptr_t)context; uint32_t uid = 0; for (ids = hpet_ids; *ids != NULL; ids++) { if (acpi_MatchHid(handle, *ids)) break; } if (*ids == NULL) return (AE_OK); if (ACPI_FAILURE(acpi_GetInteger(handle, "_UID", &uid)) || id == uid) *status = acpi_get_device(handle); return (AE_OK); } /* * Find an existing IRQ resource that matches the requested IRQ range * and return its RID. If one is not found, use a new RID. */ static int hpet_find_irq_rid(device_t dev, u_long start, u_long end) { rman_res_t irq; int error, rid; for (rid = 0;; rid++) { error = bus_get_resource(dev, SYS_RES_IRQ, rid, &irq, NULL); if (error != 0 || (start <= irq && irq <= end)) return (rid); } } static int hpet_open(struct cdev *cdev, int oflags, int devtype, struct thread *td) { struct hpet_softc *sc; sc = cdev->si_drv1; if (!sc->mmap_allow) return (EPERM); else return (0); } static int hpet_mmap(struct cdev *cdev, vm_ooffset_t offset, vm_paddr_t *paddr, int nprot, vm_memattr_t *memattr) { struct hpet_softc *sc; sc = cdev->si_drv1; if (offset > rman_get_size(sc->mem_res)) return (EINVAL); if (!sc->mmap_allow_write && (nprot & PROT_WRITE)) return (EPERM); *paddr = rman_get_start(sc->mem_res) + offset; *memattr = VM_MEMATTR_UNCACHEABLE; return (0); } /* Discover the HPET via the ACPI table of the same name. */ static void hpet_identify(driver_t *driver, device_t parent) { ACPI_TABLE_HPET *hpet; ACPI_STATUS status; device_t child; int i; /* Only one HPET device can be added. */ if (devclass_get_device(hpet_devclass, 0)) return; for (i = 1; ; i++) { /* Search for HPET table. */ status = AcpiGetTable(ACPI_SIG_HPET, i, (ACPI_TABLE_HEADER **)&hpet); if (ACPI_FAILURE(status)) return; /* Search for HPET device with same ID. */ child = NULL; AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, 100, hpet_find, NULL, (void *)(uintptr_t)hpet->Sequence, (void *)&child); /* If found - let it be probed in normal way. */ if (child) { if (bus_get_resource(child, SYS_RES_MEMORY, 0, NULL, NULL) != 0) bus_set_resource(child, SYS_RES_MEMORY, 0, hpet->Address.Address, HPET_MEM_WIDTH); continue; } /* If not - create it from table info. */ child = BUS_ADD_CHILD(parent, 2, "hpet", 0); if (child == NULL) { printf("%s: can't add child\n", __func__); continue; } bus_set_resource(child, SYS_RES_MEMORY, 0, hpet->Address.Address, HPET_MEM_WIDTH); } } static int hpet_probe(device_t dev) { - ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); + int rv; + ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); if (acpi_disabled("hpet") || acpi_hpet_disabled) return (ENXIO); - if (acpi_get_handle(dev) != NULL && - ACPI_ID_PROBE(device_get_parent(dev), dev, hpet_ids) == NULL) - return (ENXIO); - - device_set_desc(dev, "High Precision Event Timer"); - return (0); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, hpet_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "High Precision Event Timer"); + return (rv); } static int hpet_attach(device_t dev) { struct hpet_softc *sc; struct hpet_timer *t; struct make_dev_args mda; int i, j, num_msi, num_timers, num_percpu_et, num_percpu_t, cur_cpu; int pcpu_master, error; static int maxhpetet = 0; uint32_t val, val2, cvectors, dvectors; uint16_t vendor, rev; ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); sc = device_get_softc(dev); sc->dev = dev; sc->handle = acpi_get_handle(dev); sc->mem_rid = 0; sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->mem_rid, RF_ACTIVE); if (sc->mem_res == NULL) return (ENOMEM); /* Validate that we can access the whole region. */ if (rman_get_size(sc->mem_res) < HPET_MEM_WIDTH) { device_printf(dev, "memory region width %jd too small\n", rman_get_size(sc->mem_res)); bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res); return (ENXIO); } /* Be sure timer is enabled. */ hpet_enable(sc); /* Read basic statistics about the timer. */ val = bus_read_4(sc->mem_res, HPET_PERIOD); if (val == 0) { device_printf(dev, "invalid period\n"); hpet_disable(sc); bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res); return (ENXIO); } sc->freq = (1000000000000000LL + val / 2) / val; sc->caps = bus_read_4(sc->mem_res, HPET_CAPABILITIES); vendor = (sc->caps & HPET_CAP_VENDOR_ID) >> 16; rev = sc->caps & HPET_CAP_REV_ID; num_timers = 1 + ((sc->caps & HPET_CAP_NUM_TIM) >> 8); /* * ATI/AMD violates IA-PC HPET (High Precision Event Timers) * Specification and provides an off by one number * of timers/comparators. * Additionally, they use unregistered value in VENDOR_ID field. */ if (vendor == HPET_VENDID_AMD && rev < 0x10 && num_timers > 0) num_timers--; sc->num_timers = num_timers; if (bootverbose) { device_printf(dev, "vendor 0x%x, rev 0x%x, %jdHz%s, %d timers,%s\n", vendor, rev, sc->freq, (sc->caps & HPET_CAP_COUNT_SIZE) ? " 64bit" : "", num_timers, (sc->caps & HPET_CAP_LEG_RT) ? " legacy route" : ""); } for (i = 0; i < num_timers; i++) { t = &sc->t[i]; t->sc = sc; t->num = i; t->mode = TIMER_STOPPED; t->intr_rid = -1; t->irq = -1; t->pcpu_cpu = -1; t->pcpu_misrouted = 0; t->pcpu_master = -1; t->caps = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(i)); t->vectors = bus_read_4(sc->mem_res, HPET_TIMER_CAP_CNF(i) + 4); if (bootverbose) { device_printf(dev, " t%d: irqs 0x%08x (%d)%s%s%s\n", i, t->vectors, (t->caps & HPET_TCNF_INT_ROUTE) >> 9, (t->caps & HPET_TCAP_FSB_INT_DEL) ? ", MSI" : "", (t->caps & HPET_TCAP_SIZE) ? ", 64bit" : "", (t->caps & HPET_TCAP_PER_INT) ? ", periodic" : ""); } } if (testenv("debug.acpi.hpet_test")) hpet_test(sc); /* * Don't attach if the timer never increments. Since the spec * requires it to be at least 10 MHz, it has to change in 1 us. */ val = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); DELAY(1); val2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); if (val == val2) { device_printf(dev, "HPET never increments, disabling\n"); hpet_disable(sc); bus_free_resource(dev, SYS_RES_MEMORY, sc->mem_res); return (ENXIO); } /* Announce first HPET as timecounter. */ if (device_get_unit(dev) == 0) { sc->tc.tc_get_timecount = hpet_get_timecount, sc->tc.tc_counter_mask = ~0u, sc->tc.tc_name = "HPET", sc->tc.tc_quality = 950, sc->tc.tc_frequency = sc->freq; sc->tc.tc_priv = sc; sc->tc.tc_fill_vdso_timehands = hpet_vdso_timehands; #ifdef COMPAT_FREEBSD32 sc->tc.tc_fill_vdso_timehands32 = hpet_vdso_timehands32; #endif tc_init(&sc->tc); } /* If not disabled - setup and announce event timers. */ if (resource_int_value(device_get_name(dev), device_get_unit(dev), "clock", &i) == 0 && i == 0) return (0); /* Check whether we can and want legacy routing. */ sc->legacy_route = 0; resource_int_value(device_get_name(dev), device_get_unit(dev), "legacy_route", &sc->legacy_route); if ((sc->caps & HPET_CAP_LEG_RT) == 0) sc->legacy_route = 0; if (sc->legacy_route) { sc->t[0].vectors = 0; sc->t[1].vectors = 0; } /* Check what IRQs we want use. */ /* By default allow any PCI IRQs. */ sc->allowed_irqs = 0xffff0000; /* * HPETs in AMD chipsets before SB800 have problems with IRQs >= 16 * Lower are also not always working for different reasons. * SB800 fixed it, but seems do not implements level triggering * properly, that makes it very unreliable - it freezes after any * interrupt loss. Avoid legacy IRQs for AMD. */ if (vendor == HPET_VENDID_AMD || vendor == HPET_VENDID_AMD2) sc->allowed_irqs = 0x00000000; /* * NVidia MCP5x chipsets have number of unexplained interrupt * problems. For some reason, using HPET interrupts breaks HDA sound. */ if (vendor == HPET_VENDID_NVIDIA && rev <= 0x01) sc->allowed_irqs = 0x00000000; /* * ServerWorks HT1000 reported to have problems with IRQs >= 16. * Lower IRQs are working, but allowed mask is not set correctly. * Legacy_route mode works fine. */ if (vendor == HPET_VENDID_SW && rev <= 0x01) sc->allowed_irqs = 0x00000000; /* * Neither QEMU nor VirtualBox report supported IRQs correctly. * The only way to use HPET there is to specify IRQs manually * and/or use legacy_route. Legacy_route mode works on both. */ if (vm_guest) sc->allowed_irqs = 0x00000000; /* Let user override. */ resource_int_value(device_get_name(dev), device_get_unit(dev), "allowed_irqs", &sc->allowed_irqs); /* Get how much per-CPU timers we should try to provide. */ sc->per_cpu = 1; resource_int_value(device_get_name(dev), device_get_unit(dev), "per_cpu", &sc->per_cpu); num_msi = 0; sc->useirq = 0; /* Find IRQ vectors for all timers. */ cvectors = sc->allowed_irqs & 0xffff0000; dvectors = sc->allowed_irqs & 0x0000ffff; if (sc->legacy_route) dvectors &= 0x0000fefe; for (i = 0; i < num_timers; i++) { t = &sc->t[i]; if (sc->legacy_route && i < 2) t->irq = (i == 0) ? 0 : 8; #ifdef DEV_APIC else if (t->caps & HPET_TCAP_FSB_INT_DEL) { if ((j = PCIB_ALLOC_MSIX( device_get_parent(device_get_parent(dev)), dev, &t->irq))) { device_printf(dev, "Can't allocate interrupt for t%d: %d\n", i, j); } } #endif else if (dvectors & t->vectors) { t->irq = ffs(dvectors & t->vectors) - 1; dvectors &= ~(1 << t->irq); } if (t->irq >= 0) { t->intr_rid = hpet_find_irq_rid(dev, t->irq, t->irq); t->intr_res = bus_alloc_resource(dev, SYS_RES_IRQ, &t->intr_rid, t->irq, t->irq, 1, RF_ACTIVE); if (t->intr_res == NULL) { t->irq = -1; device_printf(dev, "Can't map interrupt for t%d.\n", i); } else if (bus_setup_intr(dev, t->intr_res, INTR_TYPE_CLK, hpet_intr_single, NULL, t, &t->intr_handle) != 0) { t->irq = -1; device_printf(dev, "Can't setup interrupt for t%d.\n", i); } else { bus_describe_intr(dev, t->intr_res, t->intr_handle, "t%d", i); num_msi++; } } if (t->irq < 0 && (cvectors & t->vectors) != 0) { cvectors &= t->vectors; sc->useirq |= (1 << i); } } if (sc->legacy_route && sc->t[0].irq < 0 && sc->t[1].irq < 0) sc->legacy_route = 0; if (sc->legacy_route) hpet_enable(sc); /* Group timers for per-CPU operation. */ num_percpu_et = min(num_msi / mp_ncpus, sc->per_cpu); num_percpu_t = num_percpu_et * mp_ncpus; pcpu_master = 0; cur_cpu = CPU_FIRST(); for (i = 0; i < num_timers; i++) { t = &sc->t[i]; if (t->irq >= 0 && num_percpu_t > 0) { if (cur_cpu == CPU_FIRST()) pcpu_master = i; t->pcpu_cpu = cur_cpu; t->pcpu_master = pcpu_master; sc->t[pcpu_master]. pcpu_slaves[cur_cpu] = i; bus_bind_intr(dev, t->intr_res, cur_cpu); cur_cpu = CPU_NEXT(cur_cpu); num_percpu_t--; } else if (t->irq >= 0) bus_bind_intr(dev, t->intr_res, CPU_FIRST()); } bus_write_4(sc->mem_res, HPET_ISR, 0xffffffff); sc->irq = -1; /* If at least one timer needs legacy IRQ - set it up. */ if (sc->useirq) { j = i = fls(cvectors) - 1; while (j > 0 && (cvectors & (1 << (j - 1))) != 0) j--; sc->intr_rid = hpet_find_irq_rid(dev, j, i); sc->intr_res = bus_alloc_resource(dev, SYS_RES_IRQ, &sc->intr_rid, j, i, 1, RF_SHAREABLE | RF_ACTIVE); if (sc->intr_res == NULL) device_printf(dev, "Can't map interrupt.\n"); else if (bus_setup_intr(dev, sc->intr_res, INTR_TYPE_CLK, hpet_intr, NULL, sc, &sc->intr_handle) != 0) { device_printf(dev, "Can't setup interrupt.\n"); } else { sc->irq = rman_get_start(sc->intr_res); /* Bind IRQ to BSP to avoid live migration. */ bus_bind_intr(dev, sc->intr_res, CPU_FIRST()); } } /* Program and announce event timers. */ for (i = 0; i < num_timers; i++) { t = &sc->t[i]; t->caps &= ~(HPET_TCNF_FSB_EN | HPET_TCNF_INT_ROUTE); t->caps &= ~(HPET_TCNF_VAL_SET | HPET_TCNF_INT_ENB); t->caps &= ~(HPET_TCNF_INT_TYPE); t->caps |= HPET_TCNF_32MODE; if (t->irq >= 0 && sc->legacy_route && i < 2) { /* Legacy route doesn't need more configuration. */ } else #ifdef DEV_APIC if ((t->caps & HPET_TCAP_FSB_INT_DEL) && t->irq >= 0) { uint64_t addr; uint32_t data; if (PCIB_MAP_MSI( device_get_parent(device_get_parent(dev)), dev, t->irq, &addr, &data) == 0) { bus_write_4(sc->mem_res, HPET_TIMER_FSB_ADDR(i), addr); bus_write_4(sc->mem_res, HPET_TIMER_FSB_VAL(i), data); t->caps |= HPET_TCNF_FSB_EN; } else t->irq = -2; } else #endif if (t->irq >= 0) t->caps |= (t->irq << 9); else if (sc->irq >= 0 && (t->vectors & (1 << sc->irq))) t->caps |= (sc->irq << 9) | HPET_TCNF_INT_TYPE; bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(i), t->caps); /* Skip event timers without set up IRQ. */ if (t->irq < 0 && (sc->irq < 0 || (t->vectors & (1 << sc->irq)) == 0)) continue; /* Announce the reset. */ if (maxhpetet == 0) t->et.et_name = "HPET"; else { sprintf(t->name, "HPET%d", maxhpetet); t->et.et_name = t->name; } t->et.et_flags = ET_FLAGS_PERIODIC | ET_FLAGS_ONESHOT; t->et.et_quality = 450; if (t->pcpu_master >= 0) { t->et.et_flags |= ET_FLAGS_PERCPU; t->et.et_quality += 100; } else if (mp_ncpus >= 8) t->et.et_quality -= 100; if ((t->caps & HPET_TCAP_PER_INT) == 0) t->et.et_quality -= 10; t->et.et_frequency = sc->freq; t->et.et_min_period = ((uint64_t)(HPET_MIN_CYCLES * 2) << 32) / sc->freq; t->et.et_max_period = (0xfffffffeLLU << 32) / sc->freq; t->et.et_start = hpet_start; t->et.et_stop = hpet_stop; t->et.et_priv = &sc->t[i]; if (t->pcpu_master < 0 || t->pcpu_master == i) { et_register(&t->et); maxhpetet++; } } acpi_GetInteger(sc->handle, "_UID", &sc->acpi_uid); make_dev_args_init(&mda); mda.mda_devsw = &hpet_cdevsw; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_WHEEL; mda.mda_mode = 0644; mda.mda_si_drv1 = sc; error = make_dev_s(&mda, &sc->pdev, "hpet%d", device_get_unit(dev)); if (error == 0) { sc->mmap_allow = 1; TUNABLE_INT_FETCH("hw.acpi.hpet.mmap_allow", &sc->mmap_allow); sc->mmap_allow_write = 0; TUNABLE_INT_FETCH("hw.acpi.hpet.mmap_allow_write", &sc->mmap_allow_write); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mmap_allow", CTLFLAG_RW, &sc->mmap_allow, 0, "Allow userland to memory map HPET"); SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "mmap_allow_write", CTLFLAG_RW, &sc->mmap_allow_write, 0, "Allow userland write to the HPET register space"); } else { device_printf(dev, "could not create /dev/hpet%d, error %d\n", device_get_unit(dev), error); } return (0); } static int hpet_detach(device_t dev) { ACPI_FUNCTION_TRACE((char *)(uintptr_t) __func__); /* XXX Without a tc_remove() function, we can't detach. */ return (EBUSY); } static int hpet_suspend(device_t dev) { // struct hpet_softc *sc; /* * Disable the timer during suspend. The timer will not lose * its state in S1 or S2, but we are required to disable * it. */ // sc = device_get_softc(dev); // hpet_disable(sc); return (0); } static int hpet_resume(device_t dev) { struct hpet_softc *sc; struct hpet_timer *t; int i; /* Re-enable the timer after a resume to keep the clock advancing. */ sc = device_get_softc(dev); hpet_enable(sc); /* Restart event timers that were running on suspend. */ for (i = 0; i < sc->num_timers; i++) { t = &sc->t[i]; #ifdef DEV_APIC if (t->irq >= 0 && (sc->legacy_route == 0 || i >= 2)) { uint64_t addr; uint32_t data; if (PCIB_MAP_MSI( device_get_parent(device_get_parent(dev)), dev, t->irq, &addr, &data) == 0) { bus_write_4(sc->mem_res, HPET_TIMER_FSB_ADDR(i), addr); bus_write_4(sc->mem_res, HPET_TIMER_FSB_VAL(i), data); } } #endif if (t->mode == TIMER_STOPPED) continue; t->next = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); if (t->mode == TIMER_PERIODIC && (t->caps & HPET_TCAP_PER_INT) != 0) { t->caps |= HPET_TCNF_TYPE; t->next += t->div; bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps | HPET_TCNF_VAL_SET); bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), t->next); bus_read_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num)); bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), t->div); } else { t->next += sc->freq / 1024; bus_write_4(sc->mem_res, HPET_TIMER_COMPARATOR(t->num), t->next); } bus_write_4(sc->mem_res, HPET_ISR, 1 << t->num); bus_write_4(sc->mem_res, HPET_TIMER_CAP_CNF(t->num), t->caps); } return (0); } /* Print some basic latency/rate information to assist in debugging. */ static void hpet_test(struct hpet_softc *sc) { int i; uint32_t u1, u2; struct bintime b0, b1, b2; struct timespec ts; binuptime(&b0); binuptime(&b0); binuptime(&b1); u1 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); for (i = 1; i < 1000; i++) u2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); binuptime(&b2); u2 = bus_read_4(sc->mem_res, HPET_MAIN_COUNTER); bintime_sub(&b2, &b1); bintime_sub(&b1, &b0); bintime_sub(&b2, &b1); bintime2timespec(&b2, &ts); device_printf(sc->dev, "%ld.%09ld: %u ... %u = %u\n", (long)ts.tv_sec, ts.tv_nsec, u1, u2, u2 - u1); device_printf(sc->dev, "time per call: %ld ns\n", ts.tv_nsec / 1000); } #ifdef DEV_APIC static int hpet_remap_intr(device_t dev, device_t child, u_int irq) { struct hpet_softc *sc = device_get_softc(dev); struct hpet_timer *t; uint64_t addr; uint32_t data; int error, i; for (i = 0; i < sc->num_timers; i++) { t = &sc->t[i]; if (t->irq != irq) continue; error = PCIB_MAP_MSI( device_get_parent(device_get_parent(dev)), dev, irq, &addr, &data); if (error) return (error); hpet_disable(sc); /* Stop timer to avoid interrupt loss. */ bus_write_4(sc->mem_res, HPET_TIMER_FSB_ADDR(i), addr); bus_write_4(sc->mem_res, HPET_TIMER_FSB_VAL(i), data); hpet_enable(sc); return (0); } return (ENOENT); } #endif static device_method_t hpet_methods[] = { /* Device interface */ DEVMETHOD(device_identify, hpet_identify), DEVMETHOD(device_probe, hpet_probe), DEVMETHOD(device_attach, hpet_attach), DEVMETHOD(device_detach, hpet_detach), DEVMETHOD(device_suspend, hpet_suspend), DEVMETHOD(device_resume, hpet_resume), #ifdef DEV_APIC DEVMETHOD(bus_remap_intr, hpet_remap_intr), #endif DEVMETHOD_END }; static driver_t hpet_driver = { "hpet", hpet_methods, sizeof(struct hpet_softc), }; DRIVER_MODULE(hpet, acpi, hpet_driver, hpet_devclass, 0, 0); MODULE_DEPEND(hpet, acpi, 1, 1, 1); Index: head/sys/dev/acpica/acpi_if.m =================================================================== --- head/sys/dev/acpica/acpi_if.m (revision 339753) +++ head/sys/dev/acpica/acpi_if.m (revision 339754) @@ -1,224 +1,231 @@ #- # Copyright (c) 2004 Nate Lawson # 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 #include #include INTERFACE acpi; # # Callback function for each child handle traversed in acpi_scan_children(). # # ACPI_HANDLE h: current child device being considered # # device_t *dev: pointer to the child's original device_t or NULL if there # was none. The callback should store a new device in *dev if it has # created one. The method implementation will automatically clean up the # previous device and properly associate the current ACPI_HANDLE with it. # # level: current level being scanned # # void *arg: argument passed in original call to acpi_scan_children() # # Returns: AE_OK if the scan should continue, otherwise an error # HEADER { typedef ACPI_STATUS (*acpi_scan_cb_t)(ACPI_HANDLE h, device_t *dev, int level, void *arg); struct acpi_bif; struct acpi_bst; }; # # Default implementation for acpi_id_probe(). # CODE { static char * acpi_generic_id_probe(device_t bus, device_t dev, char **ids) { return (NULL); } }; # # Check a device for a match in a list of ID strings. The strings can be # EISA PNP IDs or ACPI _HID/_CID values. # # device_t bus: parent bus for the device # # device_t dev: device being considered # # char **ids: array of ID strings to consider # -# Returns: ID string matched or NULL if no match +# char **match: Pointer to store ID string matched or NULL if no match +# pass NULL if not needed. # -METHOD char * id_probe { +# Returns: BUS_PROBE_DEFAULT if _HID match +# BUS_PROBE_LOW_PRIORITY if _CID match and not _HID match +# ENXIO if no match. +# + +METHOD int id_probe { device_t bus; device_t dev; char **ids; + char **match; } DEFAULT acpi_generic_id_probe; # # Evaluate an ACPI method or object, given its path. # # device_t bus: parent bus for the device # # device_t dev: evaluate the object relative to this device's handle. # Specify NULL to begin the search at the ACPI root. # # ACPI_STRING pathname: absolute or relative path to this object # # ACPI_OBJECT_LIST *parameters: array of arguments to pass to the object. # Specify NULL if there are none. # # ACPI_BUFFER *ret: the result (if any) of the evaluation # Specify NULL if there is none. # # Returns: AE_OK or an error value # METHOD ACPI_STATUS evaluate_object { device_t bus; device_t dev; ACPI_STRING pathname; ACPI_OBJECT_LIST *parameters; ACPI_BUFFER *ret; }; # # Get the highest power state (D0-D3) that is usable for a device when # suspending/resuming. If a bus calls this when suspending a device, it # must also call it when resuming. # # device_t bus: parent bus for the device # # device_t dev: check this device's appropriate power state # # int *dstate: if successful, contains the highest valid sleep state # # Returns: 0 on success or some other error value. # METHOD int pwr_for_sleep { device_t bus; device_t dev; int *dstate; }; # # Rescan a subtree and optionally reattach devices to handles. Users # specify a callback that is called for each ACPI_HANDLE of type Device # that is a child of "dev". # # device_t bus: parent bus for the device # # device_t dev: begin the scan starting with this device's handle. # Specify NULL to begin the scan at the ACPI root. # # int max_depth: number of levels to traverse (i.e., 1 means just the # immediate children. # # acpi_scan_cb_t user_fn: called for each child handle # # void *arg: argument to pass to the callback function # # Returns: AE_OK or an error value, based on the callback return value # METHOD ACPI_STATUS scan_children { device_t bus; device_t dev; int max_depth; acpi_scan_cb_t user_fn; void *arg; }; # # Query a given driver for its supported feature(s). This should be # called by the parent bus before the driver is probed. # # driver_t *driver: child driver # # u_int *features: returned bitmask of all supported features # STATICMETHOD int get_features { driver_t *driver; u_int *features; }; # # Read embedded controller (EC) address space # # device_t dev: EC device # u_int addr: Address to read from in EC space # UINT64 *val: Location to store read value # int width: Size of area to read in bytes # METHOD int ec_read { device_t dev; u_int addr; UINT64 *val; int width; }; # # Write embedded controller (EC) address space # # device_t dev: EC device # u_int addr: Address to write to in EC space # UINT64 val: Value to write # int width: Size of value to write in bytes # METHOD int ec_write { device_t dev; u_int addr; UINT64 val; int width; }; # # Get battery information (_BIF format) # # device_t dev: Battery device # struct acpi_bif *bif: Pointer to storage for _BIF results # METHOD int batt_get_info { device_t dev; struct acpi_bif *bif; }; # # Get battery status (_BST format) # # device_t dev: Battery device # struct acpi_bst *bst: Pointer to storage for _BST results # METHOD int batt_get_status { device_t dev; struct acpi_bst *bst; }; Index: head/sys/dev/acpica/acpi_isab.c =================================================================== --- head/sys/dev/acpica/acpi_isab.c (revision 339753) +++ head/sys/dev/acpica/acpi_isab.c (revision 339754) @@ -1,130 +1,131 @@ /*- * Copyright (c) 2003 John Baldwin * 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. */ #include __FBSDID("$FreeBSD$"); /* * ISA Bridge driver for Generic ISA Bus Devices. See section 10.7 of the * ACPI 2.0a specification for details on this device. */ #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include /* Hooks for the ACPI CA debugging infrastructure. */ #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("ISA_ACPI") struct acpi_isab_softc { device_t ap_dev; ACPI_HANDLE ap_handle; }; static int acpi_isab_probe(device_t bus); static int acpi_isab_attach(device_t bus); static int acpi_isab_read_ivar(device_t dev, device_t child, int which, uintptr_t *result); static device_method_t acpi_isab_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_isab_probe), DEVMETHOD(device_attach, acpi_isab_attach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, acpi_isab_read_ivar), DEVMETHOD(bus_alloc_resource, bus_generic_alloc_resource), DEVMETHOD(bus_release_resource, bus_generic_release_resource), DEVMETHOD(bus_activate_resource, bus_generic_activate_resource), DEVMETHOD(bus_deactivate_resource, bus_generic_deactivate_resource), DEVMETHOD(bus_setup_intr, bus_generic_setup_intr), DEVMETHOD(bus_teardown_intr, bus_generic_teardown_intr), DEVMETHOD_END }; static driver_t acpi_isab_driver = { "isab", acpi_isab_methods, sizeof(struct acpi_isab_softc), }; DRIVER_MODULE(acpi_isab, acpi, acpi_isab_driver, isab_devclass, 0, 0); MODULE_DEPEND(acpi_isab, acpi, 1, 1, 1); static int acpi_isab_probe(device_t dev) { static char *isa_ids[] = { "PNP0A05", "PNP0A06", NULL }; - + int rv; + if (acpi_disabled("isab") || - ACPI_ID_PROBE(device_get_parent(dev), dev, isa_ids) == NULL || devclass_get_device(isab_devclass, 0) != dev) return (ENXIO); - - device_set_desc(dev, "ACPI Generic ISA bridge"); - return (0); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, isa_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "ACPI Generic ISA bridge"); + return (rv); } static int acpi_isab_attach(device_t dev) { struct acpi_isab_softc *sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->ap_dev = dev; sc->ap_handle = acpi_get_handle(dev); return (isab_attach(dev)); } static int acpi_isab_read_ivar(device_t dev, device_t child, int which, uintptr_t *result) { struct acpi_isab_softc *sc = device_get_softc(dev); switch (which) { case ACPI_IVAR_HANDLE: *result = (uintptr_t)sc->ap_handle; return (0); } return (ENOENT); } Index: head/sys/dev/acpica/acpi_lid.c =================================================================== --- head/sys/dev/acpica/acpi_lid.c (revision 339753) +++ head/sys/dev/acpica/acpi_lid.c (revision 339754) @@ -1,210 +1,211 @@ /*- * Copyright (c) 2000 Takanori Watanabe * Copyright (c) 2000 Mitsuru IWASAKI * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_BUTTON ACPI_MODULE_NAME("LID") struct acpi_lid_softc { device_t lid_dev; ACPI_HANDLE lid_handle; int lid_status; /* open or closed */ }; ACPI_HANDLE acpi_lid_handle; ACPI_SERIAL_DECL(lid, "ACPI lid"); static int acpi_lid_probe(device_t dev); static int acpi_lid_attach(device_t dev); static int acpi_lid_suspend(device_t dev); static int acpi_lid_resume(device_t dev); static void acpi_lid_notify_status_changed(void *arg); static void acpi_lid_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context); static device_method_t acpi_lid_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_lid_probe), DEVMETHOD(device_attach, acpi_lid_attach), DEVMETHOD(device_suspend, acpi_lid_suspend), DEVMETHOD(device_resume, acpi_lid_resume), DEVMETHOD_END }; static driver_t acpi_lid_driver = { "acpi_lid", acpi_lid_methods, sizeof(struct acpi_lid_softc), }; static devclass_t acpi_lid_devclass; DRIVER_MODULE(acpi_lid, acpi, acpi_lid_driver, acpi_lid_devclass, 0, 0); MODULE_DEPEND(acpi_lid, acpi, 1, 1, 1); static int acpi_lid_probe(device_t dev) { static char *lid_ids[] = { "PNP0C0D", NULL }; + int rv; - if (acpi_disabled("lid") || - ACPI_ID_PROBE(device_get_parent(dev), dev, lid_ids) == NULL) + if (acpi_disabled("lid")) return (ENXIO); - - device_set_desc(dev, "Control Method Lid Switch"); - return (0); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, lid_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "Control Method Lid Switch"); + return (rv); } static int acpi_lid_attach(device_t dev) { struct acpi_prw_data prw; struct acpi_lid_softc *sc; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = device_get_softc(dev); sc->lid_dev = dev; acpi_lid_handle = sc->lid_handle = acpi_get_handle(dev); /* * If a system does not get lid events, it may make sense to change * the type to ACPI_ALL_NOTIFY. Some systems generate both a wake and * runtime notify in that case though. */ AcpiInstallNotifyHandler(sc->lid_handle, ACPI_DEVICE_NOTIFY, acpi_lid_notify_handler, sc); /* Enable the GPE for wake/runtime. */ acpi_wake_set_enable(dev, 1); if (acpi_parse_prw(sc->lid_handle, &prw) == 0) AcpiEnableGpe(prw.gpe_handle, prw.gpe_bit); /* * Export the lid status */ SYSCTL_ADD_INT(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "state", CTLFLAG_RD, &sc->lid_status, 0, "Device set to wake the system"); return (0); } static int acpi_lid_suspend(device_t dev) { return (0); } static int acpi_lid_resume(device_t dev) { return (0); } static void acpi_lid_notify_status_changed(void *arg) { struct acpi_lid_softc *sc; struct acpi_softc *acpi_sc; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); sc = (struct acpi_lid_softc *)arg; ACPI_SERIAL_BEGIN(lid); /* * Evaluate _LID and check the return value, update lid status. * Zero: The lid is closed * Non-zero: The lid is open */ status = acpi_GetInteger(sc->lid_handle, "_LID", &sc->lid_status); if (ACPI_FAILURE(status)) goto out; acpi_sc = acpi_device_get_parent_softc(sc->lid_dev); if (acpi_sc == NULL) goto out; ACPI_VPRINT(sc->lid_dev, acpi_sc, "Lid %s\n", sc->lid_status ? "opened" : "closed"); acpi_UserNotify("Lid", sc->lid_handle, sc->lid_status); if (sc->lid_status == 0) EVENTHANDLER_INVOKE(acpi_sleep_event, acpi_sc->acpi_lid_switch_sx); else EVENTHANDLER_INVOKE(acpi_wakeup_event, acpi_sc->acpi_lid_switch_sx); out: ACPI_SERIAL_END(lid); return_VOID; } /* XXX maybe not here */ #define ACPI_NOTIFY_STATUS_CHANGED 0x80 static void acpi_lid_notify_handler(ACPI_HANDLE h, UINT32 notify, void *context) { struct acpi_lid_softc *sc; ACPI_FUNCTION_TRACE_U32((char *)(uintptr_t)__func__, notify); sc = (struct acpi_lid_softc *)context; switch (notify) { case ACPI_NOTIFY_STATUS_CHANGED: AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_lid_notify_status_changed, sc); break; default: device_printf(sc->lid_dev, "unknown notify %#x\n", notify); break; } return_VOID; } Index: head/sys/dev/acpica/acpi_pci_link.c =================================================================== --- head/sys/dev/acpica/acpi_pci_link.c (revision 339753) +++ head/sys/dev/acpica/acpi_pci_link.c (revision 339754) @@ -1,1123 +1,1126 @@ /*- * Copyright (c) 2002 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "pcib_if.h" /* Hooks for the ACPI CA debugging infrastructure. */ #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("PCI_LINK") ACPI_SERIAL_DECL(pci_link, "ACPI PCI link"); #define NUM_ISA_INTERRUPTS 16 #define NUM_ACPI_INTERRUPTS 256 /* * An ACPI PCI link device may contain multiple links. Each link has its * own ACPI resource. _PRT entries specify which link is being used via * the Source Index. * * XXX: A note about Source Indices and DPFs: Currently we assume that * the DPF start and end tags are not counted towards the index that * Source Index corresponds to. Also, we assume that when DPFs are in use * they various sets overlap in terms of Indices. Here's an example * resource list indicating these assumptions: * * Resource Index * -------- ----- * I/O Port 0 * Start DPF - * IRQ 1 * MemIO 2 * Start DPF - * IRQ 1 * MemIO 2 * End DPF - * DMA Channel 3 * * The XXX is because I'm not sure if this is a valid assumption to make. */ /* States during DPF processing. */ #define DPF_OUTSIDE 0 #define DPF_FIRST 1 #define DPF_IGNORE 2 struct link; struct acpi_pci_link_softc { int pl_num_links; int pl_crs_bad; struct link *pl_links; device_t pl_dev; }; struct link { struct acpi_pci_link_softc *l_sc; uint8_t l_bios_irq; uint8_t l_irq; uint8_t l_initial_irq; UINT32 l_crs_type; int l_res_index; int l_num_irqs; int *l_irqs; int l_references; int l_routed:1; int l_isa_irq:1; ACPI_RESOURCE l_prs_template; }; struct link_count_request { int in_dpf; int count; }; struct link_res_request { struct acpi_pci_link_softc *sc; int in_dpf; int res_index; int link_index; }; static MALLOC_DEFINE(M_PCI_LINK, "pci_link", "ACPI PCI Link structures"); static int pci_link_interrupt_weights[NUM_ACPI_INTERRUPTS]; static int pci_link_bios_isa_irqs; static char *pci_link_ids[] = { "PNP0C0F", NULL }; /* * Fetch the short name associated with an ACPI handle and save it in the * passed in buffer. */ static ACPI_STATUS acpi_short_name(ACPI_HANDLE handle, char *buffer, size_t buflen) { ACPI_BUFFER buf; buf.Length = buflen; buf.Pointer = buffer; return (AcpiGetName(handle, ACPI_SINGLE_NAME, &buf)); } static int acpi_pci_link_probe(device_t dev) { char descr[28], name[12]; + int rv; /* * We explicitly do not check _STA since not all systems set it to * sensible values. */ - if (acpi_disabled("pci_link") || - ACPI_ID_PROBE(device_get_parent(dev), dev, pci_link_ids) == NULL) - return (ENXIO); - + if (acpi_disabled("pci_link")) + return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, pci_link_ids, NULL); + if (rv > 0) + return (rv); + if (ACPI_SUCCESS(acpi_short_name(acpi_get_handle(dev), name, sizeof(name)))) { snprintf(descr, sizeof(descr), "ACPI PCI Link %s", name); device_set_desc_copy(dev, descr); } else device_set_desc(dev, "ACPI PCI Link"); device_quiet(dev); - return (0); + return (rv); } static ACPI_STATUS acpi_count_irq_resources(ACPI_RESOURCE *res, void *context) { struct link_count_request *req; req = (struct link_count_request *)context; switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: switch (req->in_dpf) { case DPF_OUTSIDE: /* We've started the first DPF. */ req->in_dpf = DPF_FIRST; break; case DPF_FIRST: /* We've started the second DPF. */ req->in_dpf = DPF_IGNORE; break; } break; case ACPI_RESOURCE_TYPE_END_DEPENDENT: /* We are finished with DPF parsing. */ KASSERT(req->in_dpf != DPF_OUTSIDE, ("%s: end dpf when not parsing a dpf", __func__)); req->in_dpf = DPF_OUTSIDE; break; case ACPI_RESOURCE_TYPE_IRQ: case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: /* * Don't count resources if we are in a DPF set that we are * ignoring. */ if (req->in_dpf != DPF_IGNORE) req->count++; } return (AE_OK); } static ACPI_STATUS link_add_crs(ACPI_RESOURCE *res, void *context) { struct link_res_request *req; struct link *link; ACPI_SERIAL_ASSERT(pci_link); req = (struct link_res_request *)context; switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: switch (req->in_dpf) { case DPF_OUTSIDE: /* We've started the first DPF. */ req->in_dpf = DPF_FIRST; break; case DPF_FIRST: /* We've started the second DPF. */ panic( "%s: Multiple dependent functions within a current resource", __func__); break; } break; case ACPI_RESOURCE_TYPE_END_DEPENDENT: /* We are finished with DPF parsing. */ KASSERT(req->in_dpf != DPF_OUTSIDE, ("%s: end dpf when not parsing a dpf", __func__)); req->in_dpf = DPF_OUTSIDE; break; case ACPI_RESOURCE_TYPE_IRQ: case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: KASSERT(req->link_index < req->sc->pl_num_links, ("%s: array boundary violation", __func__)); link = &req->sc->pl_links[req->link_index]; link->l_res_index = req->res_index; link->l_crs_type = res->Type; req->link_index++; req->res_index++; /* * Only use the current value if there's one IRQ. Some * systems return multiple IRQs (which is nonsense for _CRS) * when the link hasn't been programmed. */ if (res->Type == ACPI_RESOURCE_TYPE_IRQ) { if (res->Data.Irq.InterruptCount == 1) link->l_irq = res->Data.Irq.Interrupts[0]; } else if (res->Data.ExtendedIrq.InterruptCount == 1) link->l_irq = res->Data.ExtendedIrq.Interrupts[0]; /* * An IRQ of zero means that the link isn't routed. */ if (link->l_irq == 0) link->l_irq = PCI_INVALID_IRQ; break; default: req->res_index++; } return (AE_OK); } /* * Populate the set of possible IRQs for each device. */ static ACPI_STATUS link_add_prs(ACPI_RESOURCE *res, void *context) { ACPI_RESOURCE *tmp; struct link_res_request *req; struct link *link; UINT8 *irqs = NULL; UINT32 *ext_irqs = NULL; int i, is_ext_irq = 1; ACPI_SERIAL_ASSERT(pci_link); req = (struct link_res_request *)context; switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: switch (req->in_dpf) { case DPF_OUTSIDE: /* We've started the first DPF. */ req->in_dpf = DPF_FIRST; break; case DPF_FIRST: /* We've started the second DPF. */ req->in_dpf = DPF_IGNORE; break; } break; case ACPI_RESOURCE_TYPE_END_DEPENDENT: /* We are finished with DPF parsing. */ KASSERT(req->in_dpf != DPF_OUTSIDE, ("%s: end dpf when not parsing a dpf", __func__)); req->in_dpf = DPF_OUTSIDE; break; case ACPI_RESOURCE_TYPE_IRQ: is_ext_irq = 0; /* fall through */ case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: /* * Don't parse resources if we are in a DPF set that we are * ignoring. */ if (req->in_dpf == DPF_IGNORE) break; KASSERT(req->link_index < req->sc->pl_num_links, ("%s: array boundary violation", __func__)); link = &req->sc->pl_links[req->link_index]; if (link->l_res_index == -1) { KASSERT(req->sc->pl_crs_bad, ("res_index should be set")); link->l_res_index = req->res_index; } req->link_index++; req->res_index++; /* * Stash a copy of the resource for later use when doing * _SRS. */ tmp = &link->l_prs_template; if (is_ext_irq) { bcopy(res, tmp, ACPI_RS_SIZE(tmp->Data.ExtendedIrq)); /* * XXX acpi_AppendBufferResource() cannot handle * optional data. */ bzero(&tmp->Data.ExtendedIrq.ResourceSource, sizeof(tmp->Data.ExtendedIrq.ResourceSource)); tmp->Length = ACPI_RS_SIZE(tmp->Data.ExtendedIrq); link->l_num_irqs = res->Data.ExtendedIrq.InterruptCount; ext_irqs = res->Data.ExtendedIrq.Interrupts; } else { bcopy(res, tmp, ACPI_RS_SIZE(tmp->Data.Irq)); link->l_num_irqs = res->Data.Irq.InterruptCount; irqs = res->Data.Irq.Interrupts; } if (link->l_num_irqs == 0) break; /* * Save a list of the valid IRQs. Also, if all of the * valid IRQs are ISA IRQs, then mark this link as * routed via an ISA interrupt. */ link->l_isa_irq = TRUE; link->l_irqs = malloc(sizeof(int) * link->l_num_irqs, M_PCI_LINK, M_WAITOK | M_ZERO); for (i = 0; i < link->l_num_irqs; i++) { if (is_ext_irq) { link->l_irqs[i] = ext_irqs[i]; if (ext_irqs[i] >= NUM_ISA_INTERRUPTS) link->l_isa_irq = FALSE; } else { link->l_irqs[i] = irqs[i]; if (irqs[i] >= NUM_ISA_INTERRUPTS) link->l_isa_irq = FALSE; } } /* * If this is not an ISA IRQ but _CRS used a non-extended * IRQ descriptor, don't use _CRS as a template for _SRS. */ if (!req->sc->pl_crs_bad && !link->l_isa_irq && link->l_crs_type == ACPI_RESOURCE_TYPE_IRQ) req->sc->pl_crs_bad = TRUE; break; default: if (req->in_dpf == DPF_IGNORE) break; if (req->sc->pl_crs_bad) device_printf(req->sc->pl_dev, "Warning: possible resource %d will be lost during _SRS\n", req->res_index); req->res_index++; } return (AE_OK); } static int link_valid_irq(struct link *link, int irq) { int i; ACPI_SERIAL_ASSERT(pci_link); /* Invalid interrupts are never valid. */ if (!PCI_INTERRUPT_VALID(irq)) return (FALSE); /* Any interrupt in the list of possible interrupts is valid. */ for (i = 0; i < link->l_num_irqs; i++) if (link->l_irqs[i] == irq) return (TRUE); /* * For links routed via an ISA interrupt, if the SCI is routed via * an ISA interrupt, the SCI is always treated as a valid IRQ. */ if (link->l_isa_irq && AcpiGbl_FADT.SciInterrupt == irq && irq < NUM_ISA_INTERRUPTS) return (TRUE); /* If the interrupt wasn't found in the list it is not valid. */ return (FALSE); } static void acpi_pci_link_dump(struct acpi_pci_link_softc *sc, int header, const char *tag) { struct link *link; char buf[16]; int i, j; ACPI_SERIAL_ASSERT(pci_link); if (header) { snprintf(buf, sizeof(buf), "%s:", device_get_nameunit(sc->pl_dev)); printf("%-16.16s Index IRQ Rtd Ref IRQs\n", buf); } for (i = 0; i < sc->pl_num_links; i++) { link = &sc->pl_links[i]; printf(" %-14.14s %5d %3d %c %3d ", i == 0 ? tag : "", i, link->l_irq, link->l_routed ? 'Y' : 'N', link->l_references); if (link->l_num_irqs == 0) printf(" none"); else for (j = 0; j < link->l_num_irqs; j++) printf(" %d", link->l_irqs[j]); printf("\n"); } } static int acpi_pci_link_attach(device_t dev) { struct acpi_pci_link_softc *sc; struct link_count_request creq; struct link_res_request rreq; ACPI_STATUS status; int i; sc = device_get_softc(dev); sc->pl_dev = dev; ACPI_SERIAL_BEGIN(pci_link); /* * Count the number of current resources so we know how big of * a link array to allocate. On some systems, _CRS is broken, * so for those systems try to derive the count from _PRS instead. */ creq.in_dpf = DPF_OUTSIDE; creq.count = 0; status = AcpiWalkResources(acpi_get_handle(dev), "_CRS", acpi_count_irq_resources, &creq); sc->pl_crs_bad = ACPI_FAILURE(status); if (sc->pl_crs_bad) { creq.in_dpf = DPF_OUTSIDE; creq.count = 0; status = AcpiWalkResources(acpi_get_handle(dev), "_PRS", acpi_count_irq_resources, &creq); if (ACPI_FAILURE(status)) { device_printf(dev, "Unable to parse _CRS or _PRS: %s\n", AcpiFormatException(status)); ACPI_SERIAL_END(pci_link); return (ENXIO); } } sc->pl_num_links = creq.count; if (creq.count == 0) { ACPI_SERIAL_END(pci_link); return (0); } sc->pl_links = malloc(sizeof(struct link) * sc->pl_num_links, M_PCI_LINK, M_WAITOK | M_ZERO); /* Initialize the child links. */ for (i = 0; i < sc->pl_num_links; i++) { sc->pl_links[i].l_irq = PCI_INVALID_IRQ; sc->pl_links[i].l_bios_irq = PCI_INVALID_IRQ; sc->pl_links[i].l_sc = sc; sc->pl_links[i].l_isa_irq = FALSE; sc->pl_links[i].l_res_index = -1; } /* Try to read the current settings from _CRS if it is valid. */ if (!sc->pl_crs_bad) { rreq.in_dpf = DPF_OUTSIDE; rreq.link_index = 0; rreq.res_index = 0; rreq.sc = sc; status = AcpiWalkResources(acpi_get_handle(dev), "_CRS", link_add_crs, &rreq); if (ACPI_FAILURE(status)) { device_printf(dev, "Unable to parse _CRS: %s\n", AcpiFormatException(status)); goto fail; } } /* * Try to read the possible settings from _PRS. Note that if the * _CRS is toast, we depend on having a working _PRS. However, if * _CRS works, then it is ok for _PRS to be missing. */ rreq.in_dpf = DPF_OUTSIDE; rreq.link_index = 0; rreq.res_index = 0; rreq.sc = sc; status = AcpiWalkResources(acpi_get_handle(dev), "_PRS", link_add_prs, &rreq); if (ACPI_FAILURE(status) && (status != AE_NOT_FOUND || sc->pl_crs_bad)) { device_printf(dev, "Unable to parse _PRS: %s\n", AcpiFormatException(status)); goto fail; } if (bootverbose) acpi_pci_link_dump(sc, 1, "Initial Probe"); /* Verify initial IRQs if we have _PRS. */ if (status != AE_NOT_FOUND) for (i = 0; i < sc->pl_num_links; i++) if (!link_valid_irq(&sc->pl_links[i], sc->pl_links[i].l_irq)) sc->pl_links[i].l_irq = PCI_INVALID_IRQ; if (bootverbose) acpi_pci_link_dump(sc, 0, "Validation"); /* Save initial IRQs. */ for (i = 0; i < sc->pl_num_links; i++) sc->pl_links[i].l_initial_irq = sc->pl_links[i].l_irq; /* * Try to disable this link. If successful, set the current IRQ to * zero and flags to indicate this link is not routed. If we can't * run _DIS (i.e., the method doesn't exist), assume the initial * IRQ was routed by the BIOS. */ if (ACPI_SUCCESS(AcpiEvaluateObject(acpi_get_handle(dev), "_DIS", NULL, NULL))) for (i = 0; i < sc->pl_num_links; i++) sc->pl_links[i].l_irq = PCI_INVALID_IRQ; else for (i = 0; i < sc->pl_num_links; i++) if (PCI_INTERRUPT_VALID(sc->pl_links[i].l_irq)) sc->pl_links[i].l_routed = TRUE; if (bootverbose) acpi_pci_link_dump(sc, 0, "After Disable"); ACPI_SERIAL_END(pci_link); return (0); fail: ACPI_SERIAL_END(pci_link); for (i = 0; i < sc->pl_num_links; i++) if (sc->pl_links[i].l_irqs != NULL) free(sc->pl_links[i].l_irqs, M_PCI_LINK); free(sc->pl_links, M_PCI_LINK); return (ENXIO); } /* XXX: Note that this is identical to pci_pir_search_irq(). */ static uint8_t acpi_pci_link_search_irq(int bus, int device, int pin) { uint32_t value; uint8_t func, maxfunc; /* See if we have a valid device at function 0. */ value = pci_cfgregread(bus, device, 0, PCIR_HDRTYPE, 1); if ((value & PCIM_HDRTYPE) > PCI_MAXHDRTYPE) return (PCI_INVALID_IRQ); if (value & PCIM_MFDEV) maxfunc = PCI_FUNCMAX; else maxfunc = 0; /* Scan all possible functions at this device. */ for (func = 0; func <= maxfunc; func++) { value = pci_cfgregread(bus, device, func, PCIR_DEVVENDOR, 4); if (value == 0xffffffff) continue; value = pci_cfgregread(bus, device, func, PCIR_INTPIN, 1); /* * See if it uses the pin in question. Note that the passed * in pin uses 0 for A, .. 3 for D whereas the intpin * register uses 0 for no interrupt, 1 for A, .. 4 for D. */ if (value != pin + 1) continue; value = pci_cfgregread(bus, device, func, PCIR_INTLINE, 1); if (bootverbose) printf( "ACPI: Found matching pin for %d.%d.INT%c at func %d: %d\n", bus, device, pin + 'A', func, value); if (value != PCI_INVALID_IRQ) return (value); } return (PCI_INVALID_IRQ); } /* * Find the link structure that corresponds to the resource index passed in * via 'source_index'. */ static struct link * acpi_pci_link_lookup(device_t dev, int source_index) { struct acpi_pci_link_softc *sc; int i; ACPI_SERIAL_ASSERT(pci_link); sc = device_get_softc(dev); for (i = 0; i < sc->pl_num_links; i++) if (sc->pl_links[i].l_res_index == source_index) return (&sc->pl_links[i]); return (NULL); } void acpi_pci_link_add_reference(device_t dev, int index, device_t pcib, int slot, int pin) { struct link *link; uint8_t bios_irq; uintptr_t bus; /* * Look up the PCI bus for the specified PCI bridge device. Note * that the PCI bridge device might not have any children yet. * However, looking up its bus number doesn't require a valid child * device, so we just pass NULL. */ if (BUS_READ_IVAR(pcib, NULL, PCIB_IVAR_BUS, &bus) != 0) { device_printf(pcib, "Unable to read PCI bus number"); panic("PCI bridge without a bus number"); } /* Bump the reference count. */ ACPI_SERIAL_BEGIN(pci_link); link = acpi_pci_link_lookup(dev, index); if (link == NULL) { device_printf(dev, "apparently invalid index %d\n", index); ACPI_SERIAL_END(pci_link); return; } link->l_references++; if (link->l_routed) pci_link_interrupt_weights[link->l_irq]++; /* * The BIOS only routes interrupts via ISA IRQs using the ATPICs * (8259As). Thus, if this link is routed via an ISA IRQ, go * look to see if the BIOS routed an IRQ for this link at the * indicated (bus, slot, pin). If so, we prefer that IRQ for * this link and add that IRQ to our list of known-good IRQs. * This provides a good work-around for link devices whose _CRS * method is either broken or bogus. We only use the value * returned by _CRS if we can't find a valid IRQ via this method * in fact. * * If this link is not routed via an ISA IRQ (because we are using * APIC for example), then don't bother looking up the BIOS IRQ * as if we find one it won't be valid anyway. */ if (!link->l_isa_irq) { ACPI_SERIAL_END(pci_link); return; } /* Try to find a BIOS IRQ setting from any matching devices. */ bios_irq = acpi_pci_link_search_irq(bus, slot, pin); if (!PCI_INTERRUPT_VALID(bios_irq)) { ACPI_SERIAL_END(pci_link); return; } /* Validate the BIOS IRQ. */ if (!link_valid_irq(link, bios_irq)) { device_printf(dev, "BIOS IRQ %u for %d.%d.INT%c is invalid\n", bios_irq, (int)bus, slot, pin + 'A'); } else if (!PCI_INTERRUPT_VALID(link->l_bios_irq)) { link->l_bios_irq = bios_irq; if (bios_irq < NUM_ISA_INTERRUPTS) pci_link_bios_isa_irqs |= (1 << bios_irq); if (bios_irq != link->l_initial_irq && PCI_INTERRUPT_VALID(link->l_initial_irq)) device_printf(dev, "BIOS IRQ %u does not match initial IRQ %u\n", bios_irq, link->l_initial_irq); } else if (bios_irq != link->l_bios_irq) device_printf(dev, "BIOS IRQ %u for %d.%d.INT%c does not match previous BIOS IRQ %u\n", bios_irq, (int)bus, slot, pin + 'A', link->l_bios_irq); ACPI_SERIAL_END(pci_link); } static ACPI_STATUS acpi_pci_link_srs_from_crs(struct acpi_pci_link_softc *sc, ACPI_BUFFER *srsbuf) { ACPI_RESOURCE *end, *res; ACPI_STATUS status; struct link *link; int i, in_dpf; /* Fetch the _CRS. */ ACPI_SERIAL_ASSERT(pci_link); srsbuf->Pointer = NULL; srsbuf->Length = ACPI_ALLOCATE_BUFFER; status = AcpiGetCurrentResources(acpi_get_handle(sc->pl_dev), srsbuf); if (ACPI_SUCCESS(status) && srsbuf->Pointer == NULL) status = AE_NO_MEMORY; if (ACPI_FAILURE(status)) { if (bootverbose) device_printf(sc->pl_dev, "Unable to fetch current resources: %s\n", AcpiFormatException(status)); return (status); } /* Fill in IRQ resources via link structures. */ link = sc->pl_links; i = 0; in_dpf = DPF_OUTSIDE; res = (ACPI_RESOURCE *)srsbuf->Pointer; end = (ACPI_RESOURCE *)((char *)srsbuf->Pointer + srsbuf->Length); for (;;) { switch (res->Type) { case ACPI_RESOURCE_TYPE_START_DEPENDENT: switch (in_dpf) { case DPF_OUTSIDE: /* We've started the first DPF. */ in_dpf = DPF_FIRST; break; case DPF_FIRST: /* We've started the second DPF. */ panic( "%s: Multiple dependent functions within a current resource", __func__); break; } break; case ACPI_RESOURCE_TYPE_END_DEPENDENT: /* We are finished with DPF parsing. */ KASSERT(in_dpf != DPF_OUTSIDE, ("%s: end dpf when not parsing a dpf", __func__)); in_dpf = DPF_OUTSIDE; break; case ACPI_RESOURCE_TYPE_IRQ: MPASS(i < sc->pl_num_links); res->Data.Irq.InterruptCount = 1; if (PCI_INTERRUPT_VALID(link->l_irq)) { KASSERT(link->l_irq < NUM_ISA_INTERRUPTS, ("%s: can't put non-ISA IRQ %d in legacy IRQ resource type", __func__, link->l_irq)); res->Data.Irq.Interrupts[0] = link->l_irq; } else res->Data.Irq.Interrupts[0] = 0; link++; i++; break; case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: MPASS(i < sc->pl_num_links); res->Data.ExtendedIrq.InterruptCount = 1; if (PCI_INTERRUPT_VALID(link->l_irq)) res->Data.ExtendedIrq.Interrupts[0] = link->l_irq; else res->Data.ExtendedIrq.Interrupts[0] = 0; link++; i++; break; } if (res->Type == ACPI_RESOURCE_TYPE_END_TAG) break; res = ACPI_NEXT_RESOURCE(res); if (res >= end) break; } return (AE_OK); } static ACPI_STATUS acpi_pci_link_srs_from_links(struct acpi_pci_link_softc *sc, ACPI_BUFFER *srsbuf) { ACPI_RESOURCE newres; ACPI_STATUS status; struct link *link; int i; /* Start off with an empty buffer. */ srsbuf->Pointer = NULL; link = sc->pl_links; for (i = 0; i < sc->pl_num_links; i++) { /* Add a new IRQ resource from each link. */ link = &sc->pl_links[i]; if (link->l_prs_template.Type == ACPI_RESOURCE_TYPE_IRQ) { /* Build an IRQ resource. */ bcopy(&link->l_prs_template, &newres, ACPI_RS_SIZE(newres.Data.Irq)); newres.Data.Irq.InterruptCount = 1; if (PCI_INTERRUPT_VALID(link->l_irq)) { KASSERT(link->l_irq < NUM_ISA_INTERRUPTS, ("%s: can't put non-ISA IRQ %d in legacy IRQ resource type", __func__, link->l_irq)); newres.Data.Irq.Interrupts[0] = link->l_irq; } else newres.Data.Irq.Interrupts[0] = 0; } else { /* Build an ExtIRQ resuorce. */ bcopy(&link->l_prs_template, &newres, ACPI_RS_SIZE(newres.Data.ExtendedIrq)); newres.Data.ExtendedIrq.InterruptCount = 1; if (PCI_INTERRUPT_VALID(link->l_irq)) newres.Data.ExtendedIrq.Interrupts[0] = link->l_irq; else newres.Data.ExtendedIrq.Interrupts[0] = 0; } /* Add the new resource to the end of the _SRS buffer. */ status = acpi_AppendBufferResource(srsbuf, &newres); if (ACPI_FAILURE(status)) { device_printf(sc->pl_dev, "Unable to build resources: %s\n", AcpiFormatException(status)); if (srsbuf->Pointer != NULL) AcpiOsFree(srsbuf->Pointer); return (status); } } return (AE_OK); } static ACPI_STATUS acpi_pci_link_route_irqs(device_t dev) { struct acpi_pci_link_softc *sc; ACPI_RESOURCE *resource, *end; ACPI_BUFFER srsbuf; ACPI_STATUS status; struct link *link; int i; ACPI_SERIAL_ASSERT(pci_link); sc = device_get_softc(dev); if (sc->pl_crs_bad) status = acpi_pci_link_srs_from_links(sc, &srsbuf); else status = acpi_pci_link_srs_from_crs(sc, &srsbuf); /* Write out new resources via _SRS. */ status = AcpiSetCurrentResources(acpi_get_handle(dev), &srsbuf); if (ACPI_FAILURE(status)) { device_printf(dev, "Unable to route IRQs: %s\n", AcpiFormatException(status)); AcpiOsFree(srsbuf.Pointer); return (status); } /* * Perform acpi_config_intr() on each IRQ resource if it was just * routed for the first time. */ link = sc->pl_links; i = 0; resource = (ACPI_RESOURCE *)srsbuf.Pointer; end = (ACPI_RESOURCE *)((char *)srsbuf.Pointer + srsbuf.Length); for (;;) { if (resource->Type == ACPI_RESOURCE_TYPE_END_TAG) break; switch (resource->Type) { case ACPI_RESOURCE_TYPE_IRQ: case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: MPASS(i < sc->pl_num_links); /* * Only configure the interrupt and update the * weights if this link has a valid IRQ and was * previously unrouted. */ if (!link->l_routed && PCI_INTERRUPT_VALID(link->l_irq)) { link->l_routed = TRUE; acpi_config_intr(dev, resource); pci_link_interrupt_weights[link->l_irq] += link->l_references; } link++; i++; break; } resource = ACPI_NEXT_RESOURCE(resource); if (resource >= end) break; } AcpiOsFree(srsbuf.Pointer); return (AE_OK); } static int acpi_pci_link_resume(device_t dev) { struct acpi_pci_link_softc *sc; ACPI_STATUS status; int i, routed; /* * If all of our links are routed, then restore the link via _SRS, * otherwise, disable the link via _DIS. */ ACPI_SERIAL_BEGIN(pci_link); sc = device_get_softc(dev); routed = 0; for (i = 0; i < sc->pl_num_links; i++) if (sc->pl_links[i].l_routed) routed++; if (routed == sc->pl_num_links) status = acpi_pci_link_route_irqs(dev); else { AcpiEvaluateObject(acpi_get_handle(dev), "_DIS", NULL, NULL); status = AE_OK; } ACPI_SERIAL_END(pci_link); if (ACPI_FAILURE(status)) return (ENXIO); else return (0); } /* * Pick an IRQ to use for this unrouted link. */ static uint8_t acpi_pci_link_choose_irq(device_t dev, struct link *link) { char tunable_buffer[64], link_name[5]; u_int8_t best_irq, pos_irq; int best_weight, pos_weight, i; KASSERT(!link->l_routed, ("%s: link already routed", __func__)); KASSERT(!PCI_INTERRUPT_VALID(link->l_irq), ("%s: link already has an IRQ", __func__)); /* Check for a tunable override. */ if (ACPI_SUCCESS(acpi_short_name(acpi_get_handle(dev), link_name, sizeof(link_name)))) { snprintf(tunable_buffer, sizeof(tunable_buffer), "hw.pci.link.%s.%d.irq", link_name, link->l_res_index); if (getenv_int(tunable_buffer, &i) && PCI_INTERRUPT_VALID(i)) { if (!link_valid_irq(link, i)) device_printf(dev, "Warning, IRQ %d is not listed as valid\n", i); return (i); } snprintf(tunable_buffer, sizeof(tunable_buffer), "hw.pci.link.%s.irq", link_name); if (getenv_int(tunable_buffer, &i) && PCI_INTERRUPT_VALID(i)) { if (!link_valid_irq(link, i)) device_printf(dev, "Warning, IRQ %d is not listed as valid\n", i); return (i); } } /* * If we have a valid BIOS IRQ, use that. We trust what the BIOS * says it routed over what _CRS says the link thinks is routed. */ if (PCI_INTERRUPT_VALID(link->l_bios_irq)) return (link->l_bios_irq); /* * If we don't have a BIOS IRQ but do have a valid IRQ from _CRS, * then use that. */ if (PCI_INTERRUPT_VALID(link->l_initial_irq)) return (link->l_initial_irq); /* * Ok, we have no useful hints, so we have to pick from the * possible IRQs. For ISA IRQs we only use interrupts that * have already been used by the BIOS. */ best_irq = PCI_INVALID_IRQ; best_weight = INT_MAX; for (i = 0; i < link->l_num_irqs; i++) { pos_irq = link->l_irqs[i]; if (pos_irq < NUM_ISA_INTERRUPTS && (pci_link_bios_isa_irqs & 1 << pos_irq) == 0) continue; pos_weight = pci_link_interrupt_weights[pos_irq]; if (pos_weight < best_weight) { best_weight = pos_weight; best_irq = pos_irq; } } /* * If this is an ISA IRQ, try using the SCI if it is also an ISA * interrupt as a fallback. */ if (link->l_isa_irq) { pos_irq = AcpiGbl_FADT.SciInterrupt; pos_weight = pci_link_interrupt_weights[pos_irq]; if (pos_weight < best_weight) { best_weight = pos_weight; best_irq = pos_irq; } } if (PCI_INTERRUPT_VALID(best_irq)) { if (bootverbose) device_printf(dev, "Picked IRQ %u with weight %d\n", best_irq, best_weight); } else device_printf(dev, "Unable to choose an IRQ\n"); return (best_irq); } int acpi_pci_link_route_interrupt(device_t dev, int index) { struct link *link; if (acpi_disabled("pci_link")) return (PCI_INVALID_IRQ); ACPI_SERIAL_BEGIN(pci_link); link = acpi_pci_link_lookup(dev, index); if (link == NULL) panic("%s: apparently invalid index %d", __func__, index); /* * If this link device is already routed to an interrupt, just return * the interrupt it is routed to. */ if (link->l_routed) { KASSERT(PCI_INTERRUPT_VALID(link->l_irq), ("%s: link is routed but has an invalid IRQ", __func__)); ACPI_SERIAL_END(pci_link); return (link->l_irq); } /* Choose an IRQ if we need one. */ if (!PCI_INTERRUPT_VALID(link->l_irq)) { link->l_irq = acpi_pci_link_choose_irq(dev, link); /* * Try to route the interrupt we picked. If it fails, then * assume the interrupt is not routed. */ if (PCI_INTERRUPT_VALID(link->l_irq)) { acpi_pci_link_route_irqs(dev); if (!link->l_routed) link->l_irq = PCI_INVALID_IRQ; } } ACPI_SERIAL_END(pci_link); return (link->l_irq); } /* * This is gross, but we abuse the identify routine to perform one-time * SYSINIT() style initialization for the driver. */ static void acpi_pci_link_identify(driver_t *driver, device_t parent) { /* * If the SCI is an ISA IRQ, add it to the bitmask of known good * ISA IRQs. * * XXX: If we are using the APIC, the SCI might have been * rerouted to an APIC pin in which case this is invalid. However, * if we are using the APIC, we also shouldn't be having any PCI * interrupts routed via ISA IRQs, so this is probably ok. */ if (AcpiGbl_FADT.SciInterrupt < NUM_ISA_INTERRUPTS) pci_link_bios_isa_irqs |= (1 << AcpiGbl_FADT.SciInterrupt); } static device_method_t acpi_pci_link_methods[] = { /* Device interface */ DEVMETHOD(device_identify, acpi_pci_link_identify), DEVMETHOD(device_probe, acpi_pci_link_probe), DEVMETHOD(device_attach, acpi_pci_link_attach), DEVMETHOD(device_resume, acpi_pci_link_resume), DEVMETHOD_END }; static driver_t acpi_pci_link_driver = { "pci_link", acpi_pci_link_methods, sizeof(struct acpi_pci_link_softc), }; static devclass_t pci_link_devclass; DRIVER_MODULE(acpi_pci_link, acpi, acpi_pci_link_driver, pci_link_devclass, 0, 0); MODULE_DEPEND(acpi_pci_link, acpi, 1, 1, 1); Index: head/sys/dev/acpica/acpi_resource.c =================================================================== --- head/sys/dev/acpica/acpi_resource.c (revision 339753) +++ head/sys/dev/acpica/acpi_resource.c (revision 339754) @@ -1,774 +1,777 @@ /*- * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef INTRNG #include "acpi_bus_if.h" #endif /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_BUS ACPI_MODULE_NAME("RESOURCE") struct lookup_irq_request { ACPI_RESOURCE *acpi_res; struct resource *res; int counter; int rid; int found; }; static ACPI_STATUS acpi_lookup_irq_handler(ACPI_RESOURCE *res, void *context) { struct lookup_irq_request *req; size_t len; u_int irqnum, irq; switch (res->Type) { case ACPI_RESOURCE_TYPE_IRQ: irqnum = res->Data.Irq.InterruptCount; irq = res->Data.Irq.Interrupts[0]; len = ACPI_RS_SIZE(ACPI_RESOURCE_IRQ); break; case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: irqnum = res->Data.ExtendedIrq.InterruptCount; irq = res->Data.ExtendedIrq.Interrupts[0]; len = ACPI_RS_SIZE(ACPI_RESOURCE_EXTENDED_IRQ); break; default: return (AE_OK); } if (irqnum != 1) return (AE_OK); req = (struct lookup_irq_request *)context; if (req->counter != req->rid) { req->counter++; return (AE_OK); } req->found = 1; KASSERT(irq == rman_get_start(req->res), ("IRQ resources do not match")); bcopy(res, req->acpi_res, len); return (AE_CTRL_TERMINATE); } ACPI_STATUS acpi_lookup_irq_resource(device_t dev, int rid, struct resource *res, ACPI_RESOURCE *acpi_res) { struct lookup_irq_request req; ACPI_STATUS status; req.acpi_res = acpi_res; req.res = res; req.counter = 0; req.rid = rid; req.found = 0; status = AcpiWalkResources(acpi_get_handle(dev), "_CRS", acpi_lookup_irq_handler, &req); if (ACPI_SUCCESS(status) && req.found == 0) status = AE_NOT_FOUND; return (status); } void acpi_config_intr(device_t dev, ACPI_RESOURCE *res) { u_int irq; int pol, trig; switch (res->Type) { case ACPI_RESOURCE_TYPE_IRQ: KASSERT(res->Data.Irq.InterruptCount == 1, ("%s: multiple interrupts", __func__)); irq = res->Data.Irq.Interrupts[0]; trig = res->Data.Irq.Triggering; pol = res->Data.Irq.Polarity; break; case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: KASSERT(res->Data.ExtendedIrq.InterruptCount == 1, ("%s: multiple interrupts", __func__)); irq = res->Data.ExtendedIrq.Interrupts[0]; trig = res->Data.ExtendedIrq.Triggering; pol = res->Data.ExtendedIrq.Polarity; break; default: panic("%s: bad resource type %u", __func__, res->Type); } #if defined(__amd64__) || defined(__i386__) /* * XXX: Certain BIOSes have buggy AML that specify an IRQ that is * edge-sensitive and active-lo. However, edge-sensitive IRQs * should be active-hi. Force IRQs with an ISA IRQ value to be * active-hi instead. */ if (irq < 16 && trig == ACPI_EDGE_SENSITIVE && pol == ACPI_ACTIVE_LOW) pol = ACPI_ACTIVE_HIGH; #endif BUS_CONFIG_INTR(dev, irq, (trig == ACPI_EDGE_SENSITIVE) ? INTR_TRIGGER_EDGE : INTR_TRIGGER_LEVEL, (pol == ACPI_ACTIVE_HIGH) ? INTR_POLARITY_HIGH : INTR_POLARITY_LOW); } struct acpi_resource_context { struct acpi_parse_resource_set *set; device_t dev; void *context; }; #ifdef ACPI_DEBUG_OUTPUT static const char * acpi_address_range_name(UINT8 ResourceType) { static char buf[16]; switch (ResourceType) { case ACPI_MEMORY_RANGE: return ("Memory"); case ACPI_IO_RANGE: return ("IO"); case ACPI_BUS_NUMBER_RANGE: return ("Bus Number"); default: snprintf(buf, sizeof(buf), "type %u", ResourceType); return (buf); } } #endif static ACPI_STATUS acpi_parse_resource(ACPI_RESOURCE *res, void *context) { struct acpi_parse_resource_set *set; struct acpi_resource_context *arc; UINT64 min, max, length, gran; #ifdef ACPI_DEBUG const char *name; #endif device_t dev; arc = context; dev = arc->dev; set = arc->set; switch (res->Type) { case ACPI_RESOURCE_TYPE_END_TAG: ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "EndTag\n")); break; case ACPI_RESOURCE_TYPE_FIXED_IO: if (res->Data.FixedIo.AddressLength <= 0) break; ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "FixedIo 0x%x/%d\n", res->Data.FixedIo.Address, res->Data.FixedIo.AddressLength)); set->set_ioport(dev, arc->context, res->Data.FixedIo.Address, res->Data.FixedIo.AddressLength); break; case ACPI_RESOURCE_TYPE_IO: if (res->Data.Io.AddressLength <= 0) break; if (res->Data.Io.Minimum == res->Data.Io.Maximum) { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Io 0x%x/%d\n", res->Data.Io.Minimum, res->Data.Io.AddressLength)); set->set_ioport(dev, arc->context, res->Data.Io.Minimum, res->Data.Io.AddressLength); } else { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Io 0x%x-0x%x/%d\n", res->Data.Io.Minimum, res->Data.Io.Maximum, res->Data.Io.AddressLength)); set->set_iorange(dev, arc->context, res->Data.Io.Minimum, res->Data.Io.Maximum, res->Data.Io.AddressLength, res->Data.Io.Alignment); } break; case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: if (res->Data.FixedMemory32.AddressLength <= 0) break; ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "FixedMemory32 0x%x/%d\n", res->Data.FixedMemory32.Address, res->Data.FixedMemory32.AddressLength)); set->set_memory(dev, arc->context, res->Data.FixedMemory32.Address, res->Data.FixedMemory32.AddressLength); break; case ACPI_RESOURCE_TYPE_MEMORY32: if (res->Data.Memory32.AddressLength <= 0) break; if (res->Data.Memory32.Minimum == res->Data.Memory32.Maximum) { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Memory32 0x%x/%d\n", res->Data.Memory32.Minimum, res->Data.Memory32.AddressLength)); set->set_memory(dev, arc->context, res->Data.Memory32.Minimum, res->Data.Memory32.AddressLength); } else { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Memory32 0x%x-0x%x/%d\n", res->Data.Memory32.Minimum, res->Data.Memory32.Maximum, res->Data.Memory32.AddressLength)); set->set_memoryrange(dev, arc->context, res->Data.Memory32.Minimum, res->Data.Memory32.Maximum, res->Data.Memory32.AddressLength, res->Data.Memory32.Alignment); } break; case ACPI_RESOURCE_TYPE_MEMORY24: if (res->Data.Memory24.AddressLength <= 0) break; if (res->Data.Memory24.Minimum == res->Data.Memory24.Maximum) { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Memory24 0x%x/%d\n", res->Data.Memory24.Minimum, res->Data.Memory24.AddressLength)); set->set_memory(dev, arc->context, res->Data.Memory24.Minimum, res->Data.Memory24.AddressLength); } else { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "Memory24 0x%x-0x%x/%d\n", res->Data.Memory24.Minimum, res->Data.Memory24.Maximum, res->Data.Memory24.AddressLength)); set->set_memoryrange(dev, arc->context, res->Data.Memory24.Minimum, res->Data.Memory24.Maximum, res->Data.Memory24.AddressLength, res->Data.Memory24.Alignment); } break; case ACPI_RESOURCE_TYPE_IRQ: /* * from 1.0b 6.4.2 * "This structure is repeated for each separate interrupt * required" */ set->set_irq(dev, arc->context, res->Data.Irq.Interrupts, res->Data.Irq.InterruptCount, res->Data.Irq.Triggering, res->Data.Irq.Polarity); break; case ACPI_RESOURCE_TYPE_DMA: /* * from 1.0b 6.4.3 * "This structure is repeated for each separate DMA channel * required" */ set->set_drq(dev, arc->context, res->Data.Dma.Channels, res->Data.Dma.ChannelCount); break; case ACPI_RESOURCE_TYPE_START_DEPENDENT: ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "start dependent functions\n")); set->set_start_dependent(dev, arc->context, res->Data.StartDpf.CompatibilityPriority); break; case ACPI_RESOURCE_TYPE_END_DEPENDENT: ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "end dependent functions\n")); set->set_end_dependent(dev, arc->context); break; case ACPI_RESOURCE_TYPE_ADDRESS16: case ACPI_RESOURCE_TYPE_ADDRESS32: case ACPI_RESOURCE_TYPE_ADDRESS64: case ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64: switch (res->Type) { case ACPI_RESOURCE_TYPE_ADDRESS16: gran = res->Data.Address16.Address.Granularity; min = res->Data.Address16.Address.Minimum; max = res->Data.Address16.Address.Maximum; length = res->Data.Address16.Address.AddressLength; #ifdef ACPI_DEBUG name = "Address16"; #endif break; case ACPI_RESOURCE_TYPE_ADDRESS32: gran = res->Data.Address32.Address.Granularity; min = res->Data.Address32.Address.Minimum; max = res->Data.Address32.Address.Maximum; length = res->Data.Address32.Address.AddressLength; #ifdef ACPI_DEBUG name = "Address32"; #endif break; case ACPI_RESOURCE_TYPE_ADDRESS64: gran = res->Data.Address64.Address.Granularity; min = res->Data.Address64.Address.Minimum; max = res->Data.Address64.Address.Maximum; length = res->Data.Address64.Address.AddressLength; #ifdef ACPI_DEBUG name = "Address64"; #endif break; default: KASSERT(res->Type == ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64, ("should never happen")); gran = res->Data.ExtAddress64.Address.Granularity; min = res->Data.ExtAddress64.Address.Minimum; max = res->Data.ExtAddress64.Address.Maximum; length = res->Data.ExtAddress64.Address.AddressLength; #ifdef ACPI_DEBUG name = "ExtAddress64"; #endif break; } if (length <= 0) break; if (res->Type == ACPI_RESOURCE_TYPE_EXTENDED_ADDRESS64 && res->Data.Address.ProducerConsumer != ACPI_CONSUMER) { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "ignored %s %s producer\n", name, acpi_address_range_name(res->Data.Address.ResourceType))); break; } if (res->Data.Address.ResourceType != ACPI_MEMORY_RANGE && res->Data.Address.ResourceType != ACPI_IO_RANGE) { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "ignored %s for non-memory, non-I/O\n", name)); break; } #ifdef __i386__ if (min > ULONG_MAX || (res->Data.Address.MaxAddressFixed && max > ULONG_MAX)) { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "ignored %s above 4G\n", name)); break; } if (max > ULONG_MAX) max = ULONG_MAX; #endif if (res->Data.Address.MinAddressFixed == ACPI_ADDRESS_FIXED && res->Data.Address.MaxAddressFixed == ACPI_ADDRESS_FIXED) { if (res->Data.Address.ResourceType == ACPI_MEMORY_RANGE) { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "%s/Memory 0x%jx/%ju\n", name, (uintmax_t)min, (uintmax_t)length)); set->set_memory(dev, arc->context, min, length); } else { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "%s/IO 0x%jx/%ju\n", name, (uintmax_t)min, (uintmax_t)length)); set->set_ioport(dev, arc->context, min, length); } } else { if (res->Data.Address32.ResourceType == ACPI_MEMORY_RANGE) { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "%s/Memory 0x%jx-0x%jx/%ju\n", name, (uintmax_t)min, (uintmax_t)max, (uintmax_t)length)); set->set_memoryrange(dev, arc->context, min, max, length, gran); } else { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "%s/IO 0x%jx-0x%jx/%ju\n", name, (uintmax_t)min, (uintmax_t)max, (uintmax_t)length)); set->set_iorange(dev, arc->context, min, max, length, gran); } } break; case ACPI_RESOURCE_TYPE_EXTENDED_IRQ: if (res->Data.ExtendedIrq.ProducerConsumer != ACPI_CONSUMER) { ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "ignored ExtIRQ producer\n")); break; } set->set_ext_irq(dev, arc->context, res->Data.ExtendedIrq.Interrupts, res->Data.ExtendedIrq.InterruptCount, res->Data.ExtendedIrq.Triggering, res->Data.ExtendedIrq.Polarity); break; case ACPI_RESOURCE_TYPE_VENDOR: ACPI_DEBUG_PRINT((ACPI_DB_RESOURCES, "unimplemented VendorSpecific resource\n")); break; default: break; } return (AE_OK); } /* * Fetch a device's resources and associate them with the device. * * Note that it might be nice to also locate ACPI-specific resource items, such * as GPE bits. * * We really need to split the resource-fetching code out from the * resource-parsing code, since we may want to use the parsing * code for _PRS someday. */ ACPI_STATUS acpi_parse_resources(device_t dev, ACPI_HANDLE handle, struct acpi_parse_resource_set *set, void *arg) { struct acpi_resource_context arc; ACPI_STATUS status; ACPI_FUNCTION_TRACE((char *)(uintptr_t)__func__); set->set_init(dev, arg, &arc.context); arc.set = set; arc.dev = dev; status = AcpiWalkResources(handle, "_CRS", acpi_parse_resource, &arc); if (ACPI_FAILURE(status) && status != AE_NOT_FOUND) { printf("can't fetch resources for %s - %s\n", acpi_name(handle), AcpiFormatException(status)); return_ACPI_STATUS (status); } set->set_done(dev, arc.context); return_ACPI_STATUS (AE_OK); } /* * Resource-set vectors used to attach _CRS-derived resources * to an ACPI device. */ static void acpi_res_set_init(device_t dev, void *arg, void **context); static void acpi_res_set_done(device_t dev, void *context); static void acpi_res_set_ioport(device_t dev, void *context, uint64_t base, uint64_t length); static void acpi_res_set_iorange(device_t dev, void *context, uint64_t low, uint64_t high, uint64_t length, uint64_t align); static void acpi_res_set_memory(device_t dev, void *context, uint64_t base, uint64_t length); static void acpi_res_set_memoryrange(device_t dev, void *context, uint64_t low, uint64_t high, uint64_t length, uint64_t align); static void acpi_res_set_irq(device_t dev, void *context, uint8_t *irq, int count, int trig, int pol); static void acpi_res_set_ext_irq(device_t dev, void *context, uint32_t *irq, int count, int trig, int pol); static void acpi_res_set_drq(device_t dev, void *context, uint8_t *drq, int count); static void acpi_res_set_start_dependent(device_t dev, void *context, int preference); static void acpi_res_set_end_dependent(device_t dev, void *context); struct acpi_parse_resource_set acpi_res_parse_set = { acpi_res_set_init, acpi_res_set_done, acpi_res_set_ioport, acpi_res_set_iorange, acpi_res_set_memory, acpi_res_set_memoryrange, acpi_res_set_irq, acpi_res_set_ext_irq, acpi_res_set_drq, acpi_res_set_start_dependent, acpi_res_set_end_dependent }; struct acpi_res_context { int ar_nio; int ar_nmem; int ar_nirq; int ar_ndrq; void *ar_parent; }; static void acpi_res_set_init(device_t dev, void *arg, void **context) { struct acpi_res_context *cp; if ((cp = AcpiOsAllocate(sizeof(*cp))) != NULL) { bzero(cp, sizeof(*cp)); cp->ar_parent = arg; *context = cp; } } static void acpi_res_set_done(device_t dev, void *context) { struct acpi_res_context *cp = (struct acpi_res_context *)context; if (cp == NULL) return; AcpiOsFree(cp); } static void acpi_res_set_ioport(device_t dev, void *context, uint64_t base, uint64_t length) { struct acpi_res_context *cp = (struct acpi_res_context *)context; if (cp == NULL) return; bus_set_resource(dev, SYS_RES_IOPORT, cp->ar_nio++, base, length); } static void acpi_res_set_iorange(device_t dev, void *context, uint64_t low, uint64_t high, uint64_t length, uint64_t align) { struct acpi_res_context *cp = (struct acpi_res_context *)context; if (cp == NULL) return; /* * XXX: Some BIOSes contain buggy _CRS entries where fixed I/O * ranges have the maximum base address (_MAX) to the end of the * I/O range instead of the start. These are then treated as a * relocatable I/O range rather than a fixed I/O resource. As a * workaround, treat I/O resources encoded this way as fixed I/O * ports. */ if (high == (low + length)) { if (bootverbose) device_printf(dev, "_CRS has fixed I/O port range defined as relocatable\n"); bus_set_resource(dev, SYS_RES_IOPORT, cp->ar_nio++, low, length); return; } device_printf(dev, "I/O range not supported\n"); } static void acpi_res_set_memory(device_t dev, void *context, uint64_t base, uint64_t length) { struct acpi_res_context *cp = (struct acpi_res_context *)context; if (cp == NULL) return; while (bus_get_resource_start(dev, SYS_RES_MEMORY, cp->ar_nmem)) cp->ar_nmem++; bus_set_resource(dev, SYS_RES_MEMORY, cp->ar_nmem++, base, length); } static void acpi_res_set_memoryrange(device_t dev, void *context, uint64_t low, uint64_t high, uint64_t length, uint64_t align) { struct acpi_res_context *cp = (struct acpi_res_context *)context; if (cp == NULL) return; device_printf(dev, "memory range not supported\n"); } static void acpi_res_set_irq(device_t dev, void *context, uint8_t *irq, int count, int trig, int pol) { struct acpi_res_context *cp = (struct acpi_res_context *)context; rman_res_t intr; if (cp == NULL || irq == NULL) return; /* This implements no resource relocation. */ if (count != 1) return; #ifdef INTRNG intr = ACPI_BUS_MAP_INTR(device_get_parent(dev), dev, *irq, (trig == ACPI_EDGE_SENSITIVE) ? INTR_TRIGGER_EDGE : INTR_TRIGGER_LEVEL, (pol == ACPI_ACTIVE_HIGH) ? INTR_POLARITY_HIGH : INTR_POLARITY_LOW); #else intr = *irq; #endif bus_set_resource(dev, SYS_RES_IRQ, cp->ar_nirq++, intr, 1); } static void acpi_res_set_ext_irq(device_t dev, void *context, uint32_t *irq, int count, int trig, int pol) { struct acpi_res_context *cp = (struct acpi_res_context *)context; rman_res_t intr; if (cp == NULL || irq == NULL) return; /* This implements no resource relocation. */ if (count != 1) return; #ifdef INTRNG intr = ACPI_BUS_MAP_INTR(device_get_parent(dev), dev, *irq, (trig == ACPI_EDGE_SENSITIVE) ? INTR_TRIGGER_EDGE : INTR_TRIGGER_LEVEL, (pol == ACPI_ACTIVE_HIGH) ? INTR_POLARITY_HIGH : INTR_POLARITY_LOW); #else intr = *irq; #endif bus_set_resource(dev, SYS_RES_IRQ, cp->ar_nirq++, intr, 1); } static void acpi_res_set_drq(device_t dev, void *context, uint8_t *drq, int count) { struct acpi_res_context *cp = (struct acpi_res_context *)context; if (cp == NULL || drq == NULL) return; /* This implements no resource relocation. */ if (count != 1) return; bus_set_resource(dev, SYS_RES_DRQ, cp->ar_ndrq++, *drq, 1); } static void acpi_res_set_start_dependent(device_t dev, void *context, int preference) { struct acpi_res_context *cp = (struct acpi_res_context *)context; if (cp == NULL) return; device_printf(dev, "dependent functions not supported\n"); } static void acpi_res_set_end_dependent(device_t dev, void *context) { struct acpi_res_context *cp = (struct acpi_res_context *)context; if (cp == NULL) return; device_printf(dev, "dependent functions not supported\n"); } /* * Resource-owning placeholders for IO and memory pseudo-devices. * * This code allocates system resources that will be used by ACPI * child devices. The acpi parent manages these resources through a * private rman. */ static int acpi_sysres_rid = 100; static int acpi_sysres_probe(device_t dev); static int acpi_sysres_attach(device_t dev); static device_method_t acpi_sysres_methods[] = { /* Device interface */ DEVMETHOD(device_probe, acpi_sysres_probe), DEVMETHOD(device_attach, acpi_sysres_attach), DEVMETHOD_END }; static driver_t acpi_sysres_driver = { "acpi_sysresource", acpi_sysres_methods, 0, }; static devclass_t acpi_sysres_devclass; DRIVER_MODULE(acpi_sysresource, acpi, acpi_sysres_driver, acpi_sysres_devclass, 0, 0); MODULE_DEPEND(acpi_sysresource, acpi, 1, 1, 1); static int acpi_sysres_probe(device_t dev) { static char *sysres_ids[] = { "PNP0C01", "PNP0C02", NULL }; + int rv; - if (acpi_disabled("sysresource") || - ACPI_ID_PROBE(device_get_parent(dev), dev, sysres_ids) == NULL) + if (acpi_disabled("sysresource")) return (ENXIO); - + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, sysres_ids, NULL); + if (rv > 0){ + return (rv); + } device_set_desc(dev, "System Resource"); device_quiet(dev); - return (BUS_PROBE_DEFAULT); + return (rv); } static int acpi_sysres_attach(device_t dev) { device_t bus; struct resource_list_entry *bus_rle, *dev_rle; struct resource_list *bus_rl, *dev_rl; int done, type; rman_res_t start, end, count; /* * Loop through all current resources to see if the new one overlaps * any existing ones. If so, grow the old one up and/or down * accordingly. Discard any that are wholly contained in the old. If * the resource is unique, add it to the parent. It will later go into * the rman pool. */ bus = device_get_parent(dev); dev_rl = BUS_GET_RESOURCE_LIST(bus, dev); bus_rl = BUS_GET_RESOURCE_LIST(device_get_parent(bus), bus); STAILQ_FOREACH(dev_rle, dev_rl, link) { if (dev_rle->type != SYS_RES_IOPORT && dev_rle->type != SYS_RES_MEMORY) continue; start = dev_rle->start; end = dev_rle->end; count = dev_rle->count; type = dev_rle->type; done = FALSE; STAILQ_FOREACH(bus_rle, bus_rl, link) { if (bus_rle->type != type) continue; /* New resource wholly contained in old, discard. */ if (start >= bus_rle->start && end <= bus_rle->end) break; /* New tail overlaps old head, grow existing resource downward. */ if (start < bus_rle->start && end >= bus_rle->start) { bus_rle->count += bus_rle->start - start; bus_rle->start = start; done = TRUE; } /* New head overlaps old tail, grow existing resource upward. */ if (start <= bus_rle->end && end > bus_rle->end) { bus_rle->count += end - bus_rle->end; bus_rle->end = end; done = TRUE; } /* If we adjusted the old resource, we're finished. */ if (done) break; } /* If we didn't merge with anything, add this resource. */ if (bus_rle == NULL) bus_set_resource(bus, type, acpi_sysres_rid++, start, count); } /* After merging/moving resources to the parent, free the list. */ resource_list_free(dev_rl); return (0); } Index: head/sys/dev/acpica/acpi_smbat.c =================================================================== --- head/sys/dev/acpica/acpi_smbat.c (revision 339753) +++ head/sys/dev/acpica/acpi_smbat.c (revision 339754) @@ -1,490 +1,492 @@ /*- * Copyright (c) 2005 Hans Petter Selasky * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include /* Transactions have failed after 500 ms. */ #define SMBUS_TIMEOUT 50 struct acpi_smbat_softc { uint8_t sb_base_addr; device_t ec_dev; struct acpi_bif bif; struct acpi_bst bst; struct timespec bif_lastupdated; struct timespec bst_lastupdated; }; static int acpi_smbat_probe(device_t dev); static int acpi_smbat_attach(device_t dev); static int acpi_smbat_shutdown(device_t dev); static int acpi_smbat_info_expired(struct timespec *lastupdated); static void acpi_smbat_info_updated(struct timespec *lastupdated); static int acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif); static int acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst); ACPI_SERIAL_DECL(smbat, "ACPI Smart Battery"); static SYSCTL_NODE(_debug_acpi, OID_AUTO, batt, CTLFLAG_RD, NULL, "Battery debugging"); /* On some laptops with smart batteries, enabling battery monitoring * software causes keystrokes from atkbd to be lost. This has also been * reported on Linux, and is apparently due to the keyboard and I2C line * for the battery being routed through the same chip. Whether that's * accurate or not, adding extra sleeps to the status checking code * causes the problem to go away. * * If you experience that problem, try a value of 10ms and move up * from there. */ static int batt_sleep_ms; SYSCTL_INT(_debug_acpi_batt, OID_AUTO, batt_sleep_ms, CTLFLAG_RW, &batt_sleep_ms, 0, "Sleep during battery status updates to prevent keystroke loss."); static device_method_t acpi_smbat_methods[] = { /* device interface */ DEVMETHOD(device_probe, acpi_smbat_probe), DEVMETHOD(device_attach, acpi_smbat_attach), DEVMETHOD(device_shutdown, acpi_smbat_shutdown), /* ACPI battery interface */ DEVMETHOD(acpi_batt_get_status, acpi_smbat_get_bst), DEVMETHOD(acpi_batt_get_info, acpi_smbat_get_bif), DEVMETHOD_END }; static driver_t acpi_smbat_driver = { "battery", acpi_smbat_methods, sizeof(struct acpi_smbat_softc), }; static devclass_t acpi_smbat_devclass; DRIVER_MODULE(acpi_smbat, acpi, acpi_smbat_driver, acpi_smbat_devclass, 0, 0); MODULE_DEPEND(acpi_smbat, acpi, 1, 1, 1); static int acpi_smbat_probe(device_t dev) { static char *smbat_ids[] = {"ACPI0001", "ACPI0005", NULL}; ACPI_STATUS status; + int rv; - if (acpi_disabled("smbat") || - ACPI_ID_PROBE(device_get_parent(dev), dev, smbat_ids) == NULL) + if (acpi_disabled("smbat")) return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, smbat_ids, NULL); + if (rv > 0) + return (rv); status = AcpiEvaluateObject(acpi_get_handle(dev), "_EC", NULL, NULL); if (ACPI_FAILURE(status)) return (ENXIO); - device_set_desc(dev, "ACPI Smart Battery"); - return (0); + return (rv); } static int acpi_smbat_attach(device_t dev) { struct acpi_smbat_softc *sc; uint32_t base; sc = device_get_softc(dev); if (ACPI_FAILURE(acpi_GetInteger(acpi_get_handle(dev), "_EC", &base))) { device_printf(dev, "cannot get EC base address\n"); return (ENXIO); } sc->sb_base_addr = (base >> 8) & 0xff; /* XXX Only works with one EC, but nearly all systems only have one. */ sc->ec_dev = devclass_get_device(devclass_find("acpi_ec"), 0); if (sc->ec_dev == NULL) { device_printf(dev, "cannot find EC device\n"); return (ENXIO); } timespecclear(&sc->bif_lastupdated); timespecclear(&sc->bst_lastupdated); if (acpi_battery_register(dev) != 0) { device_printf(dev, "cannot register battery\n"); return (ENXIO); } return (0); } static int acpi_smbat_shutdown(device_t dev) { acpi_battery_remove(dev); return (0); } static int acpi_smbat_info_expired(struct timespec *lastupdated) { struct timespec curtime; ACPI_SERIAL_ASSERT(smbat); if (lastupdated == NULL) return (TRUE); if (!timespecisset(lastupdated)) return (TRUE); getnanotime(&curtime); timespecsub(&curtime, lastupdated, &curtime); return (curtime.tv_sec < 0 || curtime.tv_sec > acpi_battery_get_info_expire()); } static void acpi_smbat_info_updated(struct timespec *lastupdated) { ACPI_SERIAL_ASSERT(smbat); if (lastupdated != NULL) getnanotime(lastupdated); } static int acpi_smbus_read_2(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd, uint16_t *ptr) { int error, to; UINT64 val; ACPI_SERIAL_ASSERT(smbat); if (batt_sleep_ms) AcpiOsSleep(batt_sleep_ms); val = addr; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR, val, 1); if (error) goto out; val = cmd; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD, val, 1); if (error) goto out; val = 0x09; /* | 0x80 if PEC */ error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, val, 1); if (error) goto out; if (batt_sleep_ms) AcpiOsSleep(batt_sleep_ms); for (to = SMBUS_TIMEOUT; to != 0; to--) { error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, &val, 1); if (error) goto out; if (val == 0) break; AcpiOsSleep(10); } if (to == 0) { error = ETIMEDOUT; goto out; } error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1); if (error) goto out; if (val & SMBUS_STS_MASK) { printf("%s: AE_ERROR 0x%x\n", __FUNCTION__, (int)(val & SMBUS_STS_MASK)); error = EIO; goto out; } error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA, &val, 2); if (error) goto out; *ptr = val; out: return (error); } static int acpi_smbus_read_multi_1(struct acpi_smbat_softc *sc, uint8_t addr, uint8_t cmd, uint8_t *ptr, uint16_t len) { UINT64 val; uint8_t to; int error; ACPI_SERIAL_ASSERT(smbat); if (batt_sleep_ms) AcpiOsSleep(batt_sleep_ms); val = addr; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_ADDR, val, 1); if (error) goto out; val = cmd; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_CMD, val, 1); if (error) goto out; val = 0x0B /* | 0x80 if PEC */ ; error = ACPI_EC_WRITE(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, val, 1); if (error) goto out; if (batt_sleep_ms) AcpiOsSleep(batt_sleep_ms); for (to = SMBUS_TIMEOUT; to != 0; to--) { error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_PRTCL, &val, 1); if (error) goto out; if (val == 0) break; AcpiOsSleep(10); } if (to == 0) { error = ETIMEDOUT; goto out; } error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_STS, &val, 1); if (error) goto out; if (val & SMBUS_STS_MASK) { printf("%s: AE_ERROR 0x%x\n", __FUNCTION__, (int)(val & SMBUS_STS_MASK)); error = EIO; goto out; } /* get length */ error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_BCNT, &val, 1); if (error) goto out; val = (val & 0x1f) + 1; bzero(ptr, len); if (len > val) len = val; if (batt_sleep_ms) AcpiOsSleep(batt_sleep_ms); while (len--) { error = ACPI_EC_READ(sc->ec_dev, sc->sb_base_addr + SMBUS_DATA + len, &val, 1); if (error) goto out; ptr[len] = val; if (batt_sleep_ms) AcpiOsSleep(batt_sleep_ms); } out: return (error); } static int acpi_smbat_get_bst(device_t dev, struct acpi_bst *bst) { struct acpi_smbat_softc *sc; int error; uint32_t factor; int16_t val; uint8_t addr; ACPI_SERIAL_BEGIN(smbat); addr = SMBATT_ADDRESS; error = ENXIO; sc = device_get_softc(dev); if (!acpi_smbat_info_expired(&sc->bst_lastupdated)) { error = 0; goto out; } if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val)) goto out; if (val & SMBATT_BM_CAPACITY_MODE) factor = 10; else factor = 1; /* get battery status */ if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_STATUS, &val)) goto out; sc->bst.state = 0; if (val & SMBATT_BS_DISCHARGING) sc->bst.state |= ACPI_BATT_STAT_DISCHARG; if (val & SMBATT_BS_REMAINING_CAPACITY_ALARM) sc->bst.state |= ACPI_BATT_STAT_CRITICAL; /* * If the rate is negative, it is discharging. Otherwise, * it is charging. */ if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_CURRENT, &val)) goto out; if (val > 0) { sc->bst.rate = val * factor; sc->bst.state &= ~SMBATT_BS_DISCHARGING; sc->bst.state |= ACPI_BATT_STAT_CHARGING; } else if (val < 0) sc->bst.rate = (-val) * factor; else sc->bst.rate = 0; if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_REMAINING_CAPACITY, &val)) goto out; sc->bst.cap = val * factor; if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_VOLTAGE, &val)) goto out; sc->bst.volt = val; acpi_smbat_info_updated(&sc->bst_lastupdated); error = 0; out: if (error == 0) memcpy(bst, &sc->bst, sizeof(sc->bst)); ACPI_SERIAL_END(smbat); return (error); } static int acpi_smbat_get_bif(device_t dev, struct acpi_bif *bif) { struct acpi_smbat_softc *sc; int error; uint32_t factor; uint16_t val; uint8_t addr; ACPI_SERIAL_BEGIN(smbat); addr = SMBATT_ADDRESS; error = ENXIO; sc = device_get_softc(dev); if (!acpi_smbat_info_expired(&sc->bif_lastupdated)) { error = 0; goto out; } if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_BATTERY_MODE, &val)) goto out; if (val & SMBATT_BM_CAPACITY_MODE) { factor = 10; sc->bif.units = ACPI_BIF_UNITS_MW; } else { factor = 1; sc->bif.units = ACPI_BIF_UNITS_MA; } if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_CAPACITY, &val)) goto out; sc->bif.dcap = val * factor; if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_FULL_CHARGE_CAPACITY, &val)) goto out; sc->bif.lfcap = val * factor; sc->bif.btech = 1; /* secondary (rechargeable) */ if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_DESIGN_VOLTAGE, &val)) goto out; sc->bif.dvol = val; sc->bif.wcap = sc->bif.dcap / 10; sc->bif.lcap = sc->bif.dcap / 10; sc->bif.gra1 = factor; /* not supported */ sc->bif.gra2 = factor; /* not supported */ if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_NAME, sc->bif.model, sizeof(sc->bif.model))) goto out; if (acpi_smbus_read_2(sc, addr, SMBATT_CMD_SERIAL_NUMBER, &val)) goto out; snprintf(sc->bif.serial, sizeof(sc->bif.serial), "0x%04x", val); if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_DEVICE_CHEMISTRY, sc->bif.type, sizeof(sc->bif.type))) goto out; if (acpi_smbus_read_multi_1(sc, addr, SMBATT_CMD_MANUFACTURER_DATA, sc->bif.oeminfo, sizeof(sc->bif.oeminfo))) goto out; /* XXX check if device was replugged during read? */ acpi_smbat_info_updated(&sc->bif_lastupdated); error = 0; out: if (error == 0) memcpy(bif, &sc->bif, sizeof(sc->bif)); ACPI_SERIAL_END(smbat); return (error); } Index: head/sys/dev/acpica/acpivar.h =================================================================== --- head/sys/dev/acpica/acpivar.h (revision 339753) +++ head/sys/dev/acpica/acpivar.h (revision 339754) @@ -1,532 +1,536 @@ /*- * Copyright (c) 2000 Mitsuru IWASAKI * Copyright (c) 2000 Michael Smith * Copyright (c) 2000 BSDi * 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$ */ #ifndef _ACPIVAR_H_ #define _ACPIVAR_H_ #ifdef _KERNEL #include "acpi_if.h" #include "bus_if.h" #include #ifdef INTRNG #include #endif #include #include #include #include #include #include #include #include struct apm_clone_data; struct acpi_softc { device_t acpi_dev; struct cdev *acpi_dev_t; int acpi_enabled; int acpi_sstate; int acpi_sleep_disabled; int acpi_resources_reserved; struct sysctl_ctx_list acpi_sysctl_ctx; struct sysctl_oid *acpi_sysctl_tree; int acpi_power_button_sx; int acpi_sleep_button_sx; int acpi_lid_switch_sx; int acpi_standby_sx; int acpi_suspend_sx; int acpi_sleep_delay; int acpi_s4bios; int acpi_do_disable; int acpi_verbose; int acpi_handle_reboot; vm_offset_t acpi_wakeaddr; vm_paddr_t acpi_wakephys; int acpi_next_sstate; /* Next suspend Sx state. */ struct apm_clone_data *acpi_clone; /* Pseudo-dev for devd(8). */ STAILQ_HEAD(,apm_clone_data) apm_cdevs; /* All apm/apmctl/acpi cdevs. */ struct callout susp_force_to; /* Force suspend if no acks. */ }; struct acpi_device { /* ACPI ivars */ ACPI_HANDLE ad_handle; void *ad_private; int ad_flags; int ad_cls_class; /* Resources */ struct resource_list ad_rl; }; #ifdef INTRNG struct intr_map_data_acpi { struct intr_map_data hdr; u_int irq; u_int pol; u_int trig; }; #endif /* Track device (/dev/{apm,apmctl} and /dev/acpi) notification status. */ struct apm_clone_data { STAILQ_ENTRY(apm_clone_data) entries; struct cdev *cdev; int flags; #define ACPI_EVF_NONE 0 /* /dev/apm semantics */ #define ACPI_EVF_DEVD 1 /* /dev/acpi is handled via devd(8) */ #define ACPI_EVF_WRITE 2 /* Device instance is opened writable. */ int notify_status; #define APM_EV_NONE 0 /* Device not yet aware of pending sleep. */ #define APM_EV_NOTIFIED 1 /* Device saw next sleep state. */ #define APM_EV_ACKED 2 /* Device agreed sleep can occur. */ struct acpi_softc *acpi_sc; struct selinfo sel_read; }; #define ACPI_PRW_MAX_POWERRES 8 struct acpi_prw_data { ACPI_HANDLE gpe_handle; int gpe_bit; int lowest_wake; ACPI_OBJECT power_res[ACPI_PRW_MAX_POWERRES]; int power_res_count; }; /* Flags for each device defined in the AML namespace. */ #define ACPI_FLAG_WAKE_ENABLED 0x1 /* Macros for extracting parts of a PCI address from an _ADR value. */ #define ACPI_ADR_PCI_SLOT(adr) (((adr) & 0xffff0000) >> 16) #define ACPI_ADR_PCI_FUNC(adr) ((adr) & 0xffff) /* * Entry points to ACPI from above are global functions defined in this * file, sysctls, and I/O on the control device. Entry points from below * are interrupts (the SCI), notifies, task queue threads, and the thermal * zone polling thread. * * ACPI tables and global shared data are protected by a global lock * (acpi_mutex). * * Each ACPI device can have its own driver-specific mutex for protecting * shared access to local data. The ACPI_LOCK macros handle mutexes. * * Drivers that need to serialize access to functions (e.g., to route * interrupts, get/set control paths, etc.) should use the sx lock macros * (ACPI_SERIAL). * * ACPI-CA handles its own locking and should not be called with locks held. * * The most complicated path is: * GPE -> EC runs _Qxx -> _Qxx reads EC space -> GPE */ extern struct mtx acpi_mutex; #define ACPI_LOCK(sys) mtx_lock(&sys##_mutex) #define ACPI_UNLOCK(sys) mtx_unlock(&sys##_mutex) #define ACPI_LOCK_ASSERT(sys) mtx_assert(&sys##_mutex, MA_OWNED); #define ACPI_LOCK_DECL(sys, name) \ static struct mtx sys##_mutex; \ MTX_SYSINIT(sys##_mutex, &sys##_mutex, name, MTX_DEF) #define ACPI_SERIAL_BEGIN(sys) sx_xlock(&sys##_sxlock) #define ACPI_SERIAL_END(sys) sx_xunlock(&sys##_sxlock) #define ACPI_SERIAL_ASSERT(sys) sx_assert(&sys##_sxlock, SX_XLOCKED); #define ACPI_SERIAL_DECL(sys, name) \ static struct sx sys##_sxlock; \ SX_SYSINIT(sys##_sxlock, &sys##_sxlock, name) /* * ACPI CA does not define layers for non-ACPI CA drivers. * We define some here within the range provided. */ #define ACPI_AC_ADAPTER 0x00010000 #define ACPI_BATTERY 0x00020000 #define ACPI_BUS 0x00040000 #define ACPI_BUTTON 0x00080000 #define ACPI_EC 0x00100000 #define ACPI_FAN 0x00200000 #define ACPI_POWERRES 0x00400000 #define ACPI_PROCESSOR 0x00800000 #define ACPI_THERMAL 0x01000000 #define ACPI_TIMER 0x02000000 #define ACPI_OEM 0x04000000 /* * Constants for different interrupt models used with acpi_SetIntrModel(). */ #define ACPI_INTR_PIC 0 #define ACPI_INTR_APIC 1 #define ACPI_INTR_SAPIC 2 /* * Various features and capabilities for the acpi_get_features() method. * In particular, these are used for the ACPI 3.0 _PDC and _OSC methods. * See the Intel document titled "Intel Processor Vendor-Specific ACPI", * number 302223-007. */ #define ACPI_CAP_PERF_MSRS (1 << 0) /* Intel SpeedStep PERF_CTL MSRs */ #define ACPI_CAP_C1_IO_HALT (1 << 1) /* Intel C1 "IO then halt" sequence */ #define ACPI_CAP_THR_MSRS (1 << 2) /* Intel OnDemand throttling MSRs */ #define ACPI_CAP_SMP_SAME (1 << 3) /* MP C1, Px, and Tx (all the same) */ #define ACPI_CAP_SMP_SAME_C3 (1 << 4) /* MP C2 and C3 (all the same) */ #define ACPI_CAP_SMP_DIFF_PX (1 << 5) /* MP Px (different, using _PSD) */ #define ACPI_CAP_SMP_DIFF_CX (1 << 6) /* MP Cx (different, using _CSD) */ #define ACPI_CAP_SMP_DIFF_TX (1 << 7) /* MP Tx (different, using _TSD) */ #define ACPI_CAP_SMP_C1_NATIVE (1 << 8) /* MP C1 support other than halt */ #define ACPI_CAP_SMP_C3_NATIVE (1 << 9) /* MP C2 and C3 support */ #define ACPI_CAP_PX_HW_COORD (1 << 11) /* Intel P-state HW coordination */ #define ACPI_CAP_INTR_CPPC (1 << 12) /* Native Interrupt Handling for Collaborative Processor Performance Control notifications */ #define ACPI_CAP_HW_DUTY_C (1 << 13) /* Hardware Duty Cycling */ /* * Quirk flags. * * ACPI_Q_BROKEN: Disables all ACPI support. * ACPI_Q_TIMER: Disables support for the ACPI timer. * ACPI_Q_MADT_IRQ0: Specifies that ISA IRQ 0 is wired up to pin 0 of the * first APIC and that the MADT should force that by ignoring the PC-AT * compatible flag and ignoring overrides that redirect IRQ 0 to pin 2. */ extern int acpi_quirks; #define ACPI_Q_OK 0 #define ACPI_Q_BROKEN (1 << 0) #define ACPI_Q_TIMER (1 << 1) #define ACPI_Q_MADT_IRQ0 (1 << 2) /* * Note that the low ivar values are reserved to provide * interface compatibility with ISA drivers which can also * attach to ACPI. */ #define ACPI_IVAR_HANDLE 0x100 #define ACPI_IVAR_UNUSED 0x101 /* Unused/reserved. */ #define ACPI_IVAR_PRIVATE 0x102 #define ACPI_IVAR_FLAGS 0x103 /* * Accessor functions for our ivars. Default value for BUS_READ_IVAR is * (type) 0. The accessor functions don't check return values. */ #define __ACPI_BUS_ACCESSOR(varp, var, ivarp, ivar, type) \ \ static __inline type varp ## _get_ ## var(device_t dev) \ { \ uintptr_t v = 0; \ BUS_READ_IVAR(device_get_parent(dev), dev, \ ivarp ## _IVAR_ ## ivar, &v); \ return ((type) v); \ } \ \ static __inline void varp ## _set_ ## var(device_t dev, type t) \ { \ uintptr_t v = (uintptr_t) t; \ BUS_WRITE_IVAR(device_get_parent(dev), dev, \ ivarp ## _IVAR_ ## ivar, v); \ } __ACPI_BUS_ACCESSOR(acpi, handle, ACPI, HANDLE, ACPI_HANDLE) __ACPI_BUS_ACCESSOR(acpi, private, ACPI, PRIVATE, void *) __ACPI_BUS_ACCESSOR(acpi, flags, ACPI, FLAGS, int) void acpi_fake_objhandler(ACPI_HANDLE h, void *data); static __inline device_t acpi_get_device(ACPI_HANDLE handle) { void *dev = NULL; AcpiGetData(handle, acpi_fake_objhandler, &dev); return ((device_t)dev); } static __inline ACPI_OBJECT_TYPE acpi_get_type(device_t dev) { ACPI_HANDLE h; ACPI_OBJECT_TYPE t; if ((h = acpi_get_handle(dev)) == NULL) return (ACPI_TYPE_NOT_FOUND); if (ACPI_FAILURE(AcpiGetType(h, &t))) return (ACPI_TYPE_NOT_FOUND); return (t); } /* Find the difference between two PM tick counts. */ static __inline uint32_t acpi_TimerDelta(uint32_t end, uint32_t start) { if (end < start && (AcpiGbl_FADT.Flags & ACPI_FADT_32BIT_TIMER) == 0) end |= 0x01000000; return (end - start); } #ifdef ACPI_DEBUGGER void acpi_EnterDebugger(void); #endif #ifdef ACPI_DEBUG #include #define STEP(x) do {printf x, printf("\n"); cngetc();} while (0) #else #define STEP(x) #endif #define ACPI_VPRINT(dev, acpi_sc, x...) do { \ if (acpi_get_verbose(acpi_sc)) \ device_printf(dev, x); \ } while (0) /* Values for the first status word returned by _OSC. */ #define ACPI_OSC_FAILURE (1 << 1) #define ACPI_OSC_BAD_UUID (1 << 2) #define ACPI_OSC_BAD_REVISION (1 << 3) #define ACPI_OSC_CAPS_MASKED (1 << 4) #define ACPI_DEVINFO_PRESENT(x, flags) \ (((x) & (flags)) == (flags)) #define ACPI_DEVICE_PRESENT(x) \ ACPI_DEVINFO_PRESENT(x, ACPI_STA_DEVICE_PRESENT | \ ACPI_STA_DEVICE_FUNCTIONING) #define ACPI_BATTERY_PRESENT(x) \ ACPI_DEVINFO_PRESENT(x, ACPI_STA_DEVICE_PRESENT | \ ACPI_STA_DEVICE_FUNCTIONING | ACPI_STA_BATTERY_PRESENT) /* Callback function type for walking subtables within a table. */ typedef void acpi_subtable_handler(ACPI_SUBTABLE_HEADER *, void *); BOOLEAN acpi_DeviceIsPresent(device_t dev); BOOLEAN acpi_BatteryIsPresent(device_t dev); ACPI_STATUS acpi_GetHandleInScope(ACPI_HANDLE parent, char *path, ACPI_HANDLE *result); ACPI_BUFFER *acpi_AllocBuffer(int size); ACPI_STATUS acpi_ConvertBufferToInteger(ACPI_BUFFER *bufp, UINT32 *number); ACPI_STATUS acpi_GetInteger(ACPI_HANDLE handle, char *path, UINT32 *number); ACPI_STATUS acpi_SetInteger(ACPI_HANDLE handle, char *path, UINT32 number); ACPI_STATUS acpi_ForeachPackageObject(ACPI_OBJECT *obj, void (*func)(ACPI_OBJECT *comp, void *arg), void *arg); ACPI_STATUS acpi_FindIndexedResource(ACPI_BUFFER *buf, int index, ACPI_RESOURCE **resp); ACPI_STATUS acpi_AppendBufferResource(ACPI_BUFFER *buf, ACPI_RESOURCE *res); UINT8 acpi_DSMQuery(ACPI_HANDLE h, uint8_t *uuid, int revision); ACPI_STATUS acpi_EvaluateDSM(ACPI_HANDLE handle, uint8_t *uuid, int revision, uint64_t function, union acpi_object *package, ACPI_BUFFER *out_buf); ACPI_STATUS acpi_EvaluateOSC(ACPI_HANDLE handle, uint8_t *uuid, int revision, int count, uint32_t *caps_in, uint32_t *caps_out, bool query); ACPI_STATUS acpi_OverrideInterruptLevel(UINT32 InterruptNumber); ACPI_STATUS acpi_SetIntrModel(int model); int acpi_ReqSleepState(struct acpi_softc *sc, int state); int acpi_AckSleepState(struct apm_clone_data *clone, int error); ACPI_STATUS acpi_SetSleepState(struct acpi_softc *sc, int state); int acpi_wake_set_enable(device_t dev, int enable); int acpi_parse_prw(ACPI_HANDLE h, struct acpi_prw_data *prw); ACPI_STATUS acpi_Startup(void); void acpi_UserNotify(const char *subsystem, ACPI_HANDLE h, uint8_t notify); int acpi_bus_alloc_gas(device_t dev, int *type, int *rid, ACPI_GENERIC_ADDRESS *gas, struct resource **res, u_int flags); void acpi_walk_subtables(void *first, void *end, acpi_subtable_handler *handler, void *arg); -BOOLEAN acpi_MatchHid(ACPI_HANDLE h, const char *hid); +int acpi_MatchHid(ACPI_HANDLE h, const char *hid); +#define ACPI_MATCHHID_NOMATCH 0 +#define ACPI_MATCHHID_HID 1 +#define ACPI_MATCHHID_CID 2 + struct acpi_parse_resource_set { void (*set_init)(device_t dev, void *arg, void **context); void (*set_done)(device_t dev, void *context); void (*set_ioport)(device_t dev, void *context, uint64_t base, uint64_t length); void (*set_iorange)(device_t dev, void *context, uint64_t low, uint64_t high, uint64_t length, uint64_t align); void (*set_memory)(device_t dev, void *context, uint64_t base, uint64_t length); void (*set_memoryrange)(device_t dev, void *context, uint64_t low, uint64_t high, uint64_t length, uint64_t align); void (*set_irq)(device_t dev, void *context, uint8_t *irq, int count, int trig, int pol); void (*set_ext_irq)(device_t dev, void *context, uint32_t *irq, int count, int trig, int pol); void (*set_drq)(device_t dev, void *context, uint8_t *drq, int count); void (*set_start_dependent)(device_t dev, void *context, int preference); void (*set_end_dependent)(device_t dev, void *context); }; extern struct acpi_parse_resource_set acpi_res_parse_set; int acpi_identify(void); void acpi_config_intr(device_t dev, ACPI_RESOURCE *res); ACPI_STATUS acpi_lookup_irq_resource(device_t dev, int rid, struct resource *res, ACPI_RESOURCE *acpi_res); ACPI_STATUS acpi_parse_resources(device_t dev, ACPI_HANDLE handle, struct acpi_parse_resource_set *set, void *arg); struct resource *acpi_alloc_sysres(device_t child, int type, int *rid, rman_res_t start, rman_res_t end, rman_res_t count, u_int flags); /* ACPI event handling */ UINT32 acpi_event_power_button_sleep(void *context); UINT32 acpi_event_power_button_wake(void *context); UINT32 acpi_event_sleep_button_sleep(void *context); UINT32 acpi_event_sleep_button_wake(void *context); #define ACPI_EVENT_PRI_FIRST 0 #define ACPI_EVENT_PRI_DEFAULT 10000 #define ACPI_EVENT_PRI_LAST 20000 typedef void (*acpi_event_handler_t)(void *, int); EVENTHANDLER_DECLARE(acpi_sleep_event, acpi_event_handler_t); EVENTHANDLER_DECLARE(acpi_wakeup_event, acpi_event_handler_t); /* Device power control. */ ACPI_STATUS acpi_pwr_wake_enable(ACPI_HANDLE consumer, int enable); ACPI_STATUS acpi_pwr_switch_consumer(ACPI_HANDLE consumer, int state); int acpi_device_pwr_for_sleep(device_t bus, device_t dev, int *dstate); /* APM emulation */ void acpi_apm_init(struct acpi_softc *); /* Misc. */ static __inline struct acpi_softc * acpi_device_get_parent_softc(device_t child) { device_t parent; parent = device_get_parent(child); if (parent == NULL) return (NULL); return (device_get_softc(parent)); } static __inline int acpi_get_verbose(struct acpi_softc *sc) { if (sc) return (sc->acpi_verbose); return (0); } char *acpi_name(ACPI_HANDLE handle); int acpi_avoid(ACPI_HANDLE handle); int acpi_disabled(char *subsys); int acpi_machdep_init(device_t dev); void acpi_install_wakeup_handler(struct acpi_softc *sc); int acpi_sleep_machdep(struct acpi_softc *sc, int state); int acpi_wakeup_machdep(struct acpi_softc *sc, int state, int sleep_result, int intr_enabled); int acpi_table_quirks(int *quirks); int acpi_machdep_quirks(int *quirks); uint32_t hpet_get_uid(device_t dev); /* Battery Abstraction. */ struct acpi_battinfo; int acpi_battery_register(device_t dev); int acpi_battery_remove(device_t dev); int acpi_battery_get_units(void); int acpi_battery_get_info_expire(void); int acpi_battery_bst_valid(struct acpi_bst *bst); int acpi_battery_bif_valid(struct acpi_bif *bif); int acpi_battery_get_battinfo(device_t dev, struct acpi_battinfo *info); /* Embedded controller. */ void acpi_ec_ecdt_probe(device_t); /* AC adapter interface. */ int acpi_acad_get_acline(int *); /* Package manipulation convenience functions. */ #define ACPI_PKG_VALID(pkg, size) \ ((pkg) != NULL && (pkg)->Type == ACPI_TYPE_PACKAGE && \ (pkg)->Package.Count >= (size)) int acpi_PkgInt(ACPI_OBJECT *res, int idx, UINT64 *dst); int acpi_PkgInt32(ACPI_OBJECT *res, int idx, uint32_t *dst); int acpi_PkgStr(ACPI_OBJECT *res, int idx, void *dst, size_t size); int acpi_PkgGas(device_t dev, ACPI_OBJECT *res, int idx, int *type, int *rid, struct resource **dst, u_int flags); int acpi_PkgFFH_IntelCpu(ACPI_OBJECT *res, int idx, int *vendor, int *class, uint64_t *address, int *accsize); ACPI_HANDLE acpi_GetReference(ACPI_HANDLE scope, ACPI_OBJECT *obj); /* * Base level for BUS_ADD_CHILD. Special devices are added at orders less * than this, and normal devices at or above this level. This keeps the * probe order sorted so that things like sysresource are available before * their children need them. */ #define ACPI_DEV_BASE_ORDER 100 /* Default maximum number of tasks to enqueue. */ #ifndef ACPI_MAX_TASKS #define ACPI_MAX_TASKS MAX(32, MAXCPU * 4) #endif /* Default number of task queue threads to start. */ #ifndef ACPI_MAX_THREADS #define ACPI_MAX_THREADS 3 #endif /* Use the device logging level for ktr(4). */ #define KTR_ACPI KTR_DEV SYSCTL_DECL(_debug_acpi); /* * Map a PXM to a VM domain. * * Returns the VM domain ID if found, or -1 if not found / invalid. */ int acpi_map_pxm_to_vm_domainid(int pxm); int acpi_get_cpus(device_t dev, device_t child, enum cpu_sets op, size_t setsize, cpuset_t *cpuset); int acpi_get_domain(device_t dev, device_t child, int *domain); #endif /* _KERNEL */ #endif /* !_ACPIVAR_H_ */ Index: head/sys/dev/amdgpio/amdgpio.c =================================================================== --- head/sys/dev/amdgpio/amdgpio.c (revision 339753) +++ head/sys/dev/amdgpio/amdgpio.c (revision 339754) @@ -1,468 +1,471 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2018 Advanced Micro Devices * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" #include "amdgpio.h" static struct resource_spec amdgpio_spec[] = { { SYS_RES_MEMORY, 0, RF_ACTIVE }, { -1, 0, 0 } }; static inline uint32_t amdgpio_read_4(struct amdgpio_softc *sc, bus_size_t off) { return (bus_read_4(sc->sc_res[0], off)); } static inline void amdgpio_write_4(struct amdgpio_softc *sc, bus_size_t off, uint32_t val) { bus_write_4(sc->sc_res[0], off, val); } static bool amdgpio_is_pin_output(struct amdgpio_softc *sc, uint32_t pin) { uint32_t reg, val; bool ret; /* Get the current pin state */ AMDGPIO_LOCK(sc); reg = AMDGPIO_PIN_REGISTER(pin); val = amdgpio_read_4(sc, reg); if (val & BIT(OUTPUT_ENABLE_OFF)) ret = true; else ret = false; AMDGPIO_UNLOCK(sc); return (ret); } static device_t amdgpio_get_bus(device_t dev) { struct amdgpio_softc *sc; sc = device_get_softc(dev); dprintf("busdev %p\n", sc->sc_busdev); return (sc->sc_busdev); } static int amdgpio_pin_max(device_t dev, int *maxpin) { struct amdgpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->sc_npins - 1; dprintf("npins %d maxpin %d\n", sc->sc_npins, *maxpin); return (0); } static bool amdgpio_valid_pin(struct amdgpio_softc *sc, int pin) { dprintf("pin %d\n", pin); if (sc->sc_res[0] == NULL) return (false); if ((sc->sc_gpio_pins[pin].gp_pin == pin) && (sc->sc_gpio_pins[pin].gp_caps != 0)) return (true); return (false); } static int amdgpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct amdgpio_softc *sc; dprintf("pin %d\n", pin); sc = device_get_softc(dev); if (!amdgpio_valid_pin(sc, pin)) return (EINVAL); /* Set a very simple name */ snprintf(name, GPIOMAXNAME, "%s", sc->sc_gpio_pins[pin].gp_name); name[GPIOMAXNAME - 1] = '\0'; dprintf("pin %d name %s\n", pin, name); return (0); } static int amdgpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct amdgpio_softc *sc; sc = device_get_softc(dev); dprintf("pin %d\n", pin); if (!amdgpio_valid_pin(sc, pin)) return (EINVAL); *caps = sc->sc_gpio_pins[pin].gp_caps; dprintf("pin %d caps 0x%x\n", pin, *caps); return (0); } static int amdgpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct amdgpio_softc *sc; sc = device_get_softc(dev); dprintf("pin %d\n", pin); if (!amdgpio_valid_pin(sc, pin)) return (EINVAL); AMDGPIO_LOCK(sc); *flags = sc->sc_gpio_pins[pin].gp_flags; dprintf("pin %d flags 0x%x\n", pin, *flags); AMDGPIO_UNLOCK(sc); return (0); } static int amdgpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct amdgpio_softc *sc; uint32_t reg, val, allowed; sc = device_get_softc(dev); dprintf("pin %d flags 0x%x\n", pin, flags); if (!amdgpio_valid_pin(sc, pin)) return (EINVAL); allowed = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; /* * Only directtion flag allowed */ if (flags & ~allowed) return (EINVAL); /* * Not both directions simultaneously */ if ((flags & allowed) == allowed) return (EINVAL); /* Set the GPIO mode and state */ AMDGPIO_LOCK(sc); reg = AMDGPIO_PIN_REGISTER(pin); val = amdgpio_read_4(sc, reg); if (flags & GPIO_PIN_INPUT) { val &= ~BIT(OUTPUT_ENABLE_OFF); sc->sc_gpio_pins[pin].gp_flags = GPIO_PIN_INPUT; } else { val |= BIT(OUTPUT_ENABLE_OFF); sc->sc_gpio_pins[pin].gp_flags = GPIO_PIN_OUTPUT; } amdgpio_write_4(sc, reg, val); dprintf("pin %d flags 0x%x val 0x%x gp_flags 0x%x\n", pin, flags, val, sc->sc_gpio_pins[pin].gp_flags); AMDGPIO_UNLOCK(sc); return (0); } static int amdgpio_pin_get(device_t dev, uint32_t pin, unsigned int *value) { struct amdgpio_softc *sc; uint32_t reg, val; sc = device_get_softc(dev); dprintf("pin %d\n", pin); if (!amdgpio_valid_pin(sc, pin)) return (EINVAL); *value = 0; AMDGPIO_LOCK(sc); reg = AMDGPIO_PIN_REGISTER(pin); val = amdgpio_read_4(sc, reg); if (val & BIT(OUTPUT_VALUE_OFF)) *value = GPIO_PIN_HIGH; else *value = GPIO_PIN_LOW; dprintf("pin %d value 0x%x\n", pin, *value); AMDGPIO_UNLOCK(sc); return (0); } static int amdgpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct amdgpio_softc *sc; uint32_t reg, val; sc = device_get_softc(dev); dprintf("pin %d value 0x%x\n", pin, value); if (!amdgpio_valid_pin(sc, pin)) return (EINVAL); if (!amdgpio_is_pin_output(sc, pin)) return (EINVAL); AMDGPIO_LOCK(sc); reg = AMDGPIO_PIN_REGISTER(pin); val = amdgpio_read_4(sc, reg); if (value == GPIO_PIN_LOW) val &= ~BIT(OUTPUT_VALUE_OFF); else val |= BIT(OUTPUT_VALUE_OFF); amdgpio_write_4(sc, reg, val); dprintf("pin %d value 0x%x val 0x%x\n", pin, value, val); AMDGPIO_UNLOCK(sc); return (0); } static int amdgpio_pin_toggle(device_t dev, uint32_t pin) { struct amdgpio_softc *sc; uint32_t reg, val; sc = device_get_softc(dev); dprintf("pin %d\n", pin); if (!amdgpio_valid_pin(sc, pin)) return (EINVAL); if (!amdgpio_is_pin_output(sc, pin)) return (EINVAL); /* Toggle the pin */ AMDGPIO_LOCK(sc); reg = AMDGPIO_PIN_REGISTER(pin); val = amdgpio_read_4(sc, reg); dprintf("pin %d value before 0x%x\n", pin, val); val = val ^ BIT(OUTPUT_VALUE_OFF); dprintf("pin %d value after 0x%x\n", pin, val); amdgpio_write_4(sc, reg, val); AMDGPIO_UNLOCK(sc); return (0); } static int amdgpio_probe(device_t dev) { static char *gpio_ids[] = { "AMD0030", "AMDI0030", NULL }; - - if (acpi_disabled("gpio") || - ACPI_ID_PROBE(device_get_parent(dev), dev, gpio_ids) == NULL) - return (ENXIO); - - device_set_desc(dev, "AMD GPIO Controller"); - return (0); + int rv; + + if (acpi_disabled("gpio")) + return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, gpio_ids, NULL); + + if (rv <= 0) + device_set_desc(dev, "AMD GPIO Controller"); + + return (rv); } static int amdgpio_attach(device_t dev) { struct amdgpio_softc *sc; int i, pin, bank; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_handle = acpi_get_handle(dev); AMDGPIO_LOCK_INIT(sc); sc->sc_nbanks = AMD_GPIO_NUM_PIN_BANK; sc->sc_npins = AMD_GPIO_PINS_MAX; sc->sc_bank_prefix = AMD_GPIO_PREFIX; sc->sc_pin_info = kernzp_pins; sc->sc_ngroups = nitems(kernzp_groups); sc->sc_groups = kernzp_groups; if (bus_alloc_resources(dev, amdgpio_spec, sc->sc_res)) { device_printf(dev, "could not allocate resources\n"); goto err_rsrc; } sc->sc_bst = rman_get_bustag(sc->sc_res[0]); sc->sc_bsh = rman_get_bushandle(sc->sc_res[0]); /* Initialize all possible pins to be Invalid */ for (i = 0; i < AMD_GPIO_PINS_MAX ; i++) { snprintf(sc->sc_gpio_pins[i].gp_name, GPIOMAXNAME, "Unexposed PIN %d\n", i); sc->sc_gpio_pins[i].gp_pin = -1; sc->sc_gpio_pins[i].gp_caps = 0; sc->sc_gpio_pins[i].gp_flags = 0; } /* Initialize only driver exposed pins with appropriate capabilities */ for (i = 0; i < AMD_GPIO_PINS_EXPOSED ; i++) { pin = kernzp_pins[i].pin_num; bank = pin/AMD_GPIO_PINS_PER_BANK; snprintf(sc->sc_gpio_pins[pin].gp_name, GPIOMAXNAME, "%s%d_%s\n", AMD_GPIO_PREFIX, bank, kernzp_pins[i].pin_name); sc->sc_gpio_pins[pin].gp_pin = pin; sc->sc_gpio_pins[pin].gp_caps = AMDGPIO_DEFAULT_CAPS; sc->sc_gpio_pins[pin].gp_flags = (amdgpio_is_pin_output(sc, pin)? GPIO_PIN_OUTPUT : GPIO_PIN_INPUT); } sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) { device_printf(dev, "could not attach gpiobus\n"); goto err_bus; } return (0); err_bus: bus_release_resources(dev, amdgpio_spec, sc->sc_res); err_rsrc: AMDGPIO_LOCK_DESTROY(sc); return (ENXIO); } static int amdgpio_detach(device_t dev) { struct amdgpio_softc *sc; sc = device_get_softc(dev); if (sc->sc_busdev) gpiobus_detach_bus(dev); bus_release_resources(dev, amdgpio_spec, sc->sc_res); AMDGPIO_LOCK_DESTROY(sc); return (0); } static device_method_t amdgpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, amdgpio_probe), DEVMETHOD(device_attach, amdgpio_attach), DEVMETHOD(device_detach, amdgpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, amdgpio_get_bus), DEVMETHOD(gpio_pin_max, amdgpio_pin_max), DEVMETHOD(gpio_pin_getname, amdgpio_pin_getname), DEVMETHOD(gpio_pin_getcaps, amdgpio_pin_getcaps), DEVMETHOD(gpio_pin_getflags, amdgpio_pin_getflags), DEVMETHOD(gpio_pin_setflags, amdgpio_pin_setflags), DEVMETHOD(gpio_pin_get, amdgpio_pin_get), DEVMETHOD(gpio_pin_set, amdgpio_pin_set), DEVMETHOD(gpio_pin_toggle, amdgpio_pin_toggle), DEVMETHOD_END }; static driver_t amdgpio_driver = { "gpio", amdgpio_methods, sizeof(struct amdgpio_softc), }; static devclass_t amdgpio_devclass; DRIVER_MODULE(amdgpio, acpi, amdgpio_driver, amdgpio_devclass, 0, 0); MODULE_DEPEND(amdgpio, acpi, 1, 1, 1); MODULE_DEPEND(amdgpio, gpiobus, 1, 1, 1); MODULE_VERSION(amdgpio, 1); Index: head/sys/dev/asmc/asmc.c =================================================================== --- head/sys/dev/asmc/asmc.c (revision 339753) +++ head/sys/dev/asmc/asmc.c (revision 339754) @@ -1,1378 +1,1380 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2007, 2008 Rui Paulo * 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 ``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 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. * */ /* * Driver for Apple's System Management Console (SMC). * SMC can be found on the MacBook, MacBook Pro and Mac Mini. * * Inspired by the Linux applesmc driver. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Device interface. */ static int asmc_probe(device_t dev); static int asmc_attach(device_t dev); static int asmc_detach(device_t dev); static int asmc_resume(device_t dev); /* * SMC functions. */ static int asmc_init(device_t dev); static int asmc_command(device_t dev, uint8_t command); static int asmc_wait(device_t dev, uint8_t val); static int asmc_wait_ack(device_t dev, uint8_t val, int amount); static int asmc_key_write(device_t dev, const char *key, uint8_t *buf, uint8_t len); static int asmc_key_read(device_t dev, const char *key, uint8_t *buf, uint8_t); static int asmc_fan_count(device_t dev); static int asmc_fan_getvalue(device_t dev, const char *key, int fan); static int asmc_fan_setvalue(device_t dev, const char *key, int fan, int speed); static int asmc_temp_getvalue(device_t dev, const char *key); static int asmc_sms_read(device_t, const char *key, int16_t *val); static void asmc_sms_calibrate(device_t dev); static int asmc_sms_intrfast(void *arg); static void asmc_sms_printintr(device_t dev, uint8_t); static void asmc_sms_task(void *arg, int pending); #ifdef DEBUG void asmc_dumpall(device_t); static int asmc_key_dump(device_t, int); #endif /* * Model functions. */ static int asmc_mb_sysctl_fanid(SYSCTL_HANDLER_ARGS); static int asmc_mb_sysctl_fanspeed(SYSCTL_HANDLER_ARGS); static int asmc_mb_sysctl_fansafespeed(SYSCTL_HANDLER_ARGS); static int asmc_mb_sysctl_fanminspeed(SYSCTL_HANDLER_ARGS); static int asmc_mb_sysctl_fanmaxspeed(SYSCTL_HANDLER_ARGS); static int asmc_mb_sysctl_fantargetspeed(SYSCTL_HANDLER_ARGS); static int asmc_temp_sysctl(SYSCTL_HANDLER_ARGS); static int asmc_mb_sysctl_sms_x(SYSCTL_HANDLER_ARGS); static int asmc_mb_sysctl_sms_y(SYSCTL_HANDLER_ARGS); static int asmc_mb_sysctl_sms_z(SYSCTL_HANDLER_ARGS); static int asmc_mbp_sysctl_light_left(SYSCTL_HANDLER_ARGS); static int asmc_mbp_sysctl_light_right(SYSCTL_HANDLER_ARGS); static int asmc_mbp_sysctl_light_control(SYSCTL_HANDLER_ARGS); struct asmc_model { const char *smc_model; /* smbios.system.product env var. */ const char *smc_desc; /* driver description */ /* Helper functions */ int (*smc_sms_x)(SYSCTL_HANDLER_ARGS); int (*smc_sms_y)(SYSCTL_HANDLER_ARGS); int (*smc_sms_z)(SYSCTL_HANDLER_ARGS); int (*smc_fan_id)(SYSCTL_HANDLER_ARGS); int (*smc_fan_speed)(SYSCTL_HANDLER_ARGS); int (*smc_fan_safespeed)(SYSCTL_HANDLER_ARGS); int (*smc_fan_minspeed)(SYSCTL_HANDLER_ARGS); int (*smc_fan_maxspeed)(SYSCTL_HANDLER_ARGS); int (*smc_fan_targetspeed)(SYSCTL_HANDLER_ARGS); int (*smc_light_left)(SYSCTL_HANDLER_ARGS); int (*smc_light_right)(SYSCTL_HANDLER_ARGS); int (*smc_light_control)(SYSCTL_HANDLER_ARGS); const char *smc_temps[ASMC_TEMP_MAX]; const char *smc_tempnames[ASMC_TEMP_MAX]; const char *smc_tempdescs[ASMC_TEMP_MAX]; }; static struct asmc_model *asmc_match(device_t dev); #define ASMC_SMS_FUNCS asmc_mb_sysctl_sms_x, asmc_mb_sysctl_sms_y, \ asmc_mb_sysctl_sms_z #define ASMC_SMS_FUNCS_DISABLED NULL,NULL,NULL #define ASMC_FAN_FUNCS asmc_mb_sysctl_fanid, asmc_mb_sysctl_fanspeed, asmc_mb_sysctl_fansafespeed, \ asmc_mb_sysctl_fanminspeed, \ asmc_mb_sysctl_fanmaxspeed, \ asmc_mb_sysctl_fantargetspeed #define ASMC_FAN_FUNCS2 asmc_mb_sysctl_fanid, asmc_mb_sysctl_fanspeed, NULL, \ asmc_mb_sysctl_fanminspeed, \ asmc_mb_sysctl_fanmaxspeed, \ asmc_mb_sysctl_fantargetspeed #define ASMC_LIGHT_FUNCS asmc_mbp_sysctl_light_left, \ asmc_mbp_sysctl_light_right, \ asmc_mbp_sysctl_light_control struct asmc_model asmc_models[] = { { "MacBook1,1", "Apple SMC MacBook Core Duo", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, NULL, NULL, NULL, ASMC_MB_TEMPS, ASMC_MB_TEMPNAMES, ASMC_MB_TEMPDESCS }, { "MacBook2,1", "Apple SMC MacBook Core 2 Duo", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, NULL, NULL, NULL, ASMC_MB_TEMPS, ASMC_MB_TEMPNAMES, ASMC_MB_TEMPDESCS }, { "MacBook3,1", "Apple SMC MacBook Core 2 Duo", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, NULL, NULL, NULL, ASMC_MB31_TEMPS, ASMC_MB31_TEMPNAMES, ASMC_MB31_TEMPDESCS }, { "MacBookPro1,1", "Apple SMC MacBook Pro Core Duo (15-inch)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP_TEMPS, ASMC_MBP_TEMPNAMES, ASMC_MBP_TEMPDESCS }, { "MacBookPro1,2", "Apple SMC MacBook Pro Core Duo (17-inch)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP_TEMPS, ASMC_MBP_TEMPNAMES, ASMC_MBP_TEMPDESCS }, { "MacBookPro2,1", "Apple SMC MacBook Pro Core 2 Duo (17-inch)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP_TEMPS, ASMC_MBP_TEMPNAMES, ASMC_MBP_TEMPDESCS }, { "MacBookPro2,2", "Apple SMC MacBook Pro Core 2 Duo (15-inch)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP_TEMPS, ASMC_MBP_TEMPNAMES, ASMC_MBP_TEMPDESCS }, { "MacBookPro3,1", "Apple SMC MacBook Pro Core 2 Duo (15-inch LED)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP_TEMPS, ASMC_MBP_TEMPNAMES, ASMC_MBP_TEMPDESCS }, { "MacBookPro3,2", "Apple SMC MacBook Pro Core 2 Duo (17-inch HD)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP_TEMPS, ASMC_MBP_TEMPNAMES, ASMC_MBP_TEMPDESCS }, { "MacBookPro4,1", "Apple SMC MacBook Pro Core 2 Duo (Penryn)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP4_TEMPS, ASMC_MBP4_TEMPNAMES, ASMC_MBP4_TEMPDESCS }, { "MacBookPro5,1", "Apple SMC MacBook Pro Core 2 Duo (2008/2009)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP5_TEMPS, ASMC_MBP5_TEMPNAMES, ASMC_MBP5_TEMPDESCS }, { "MacBookPro8,2", "Apple SMC MacBook Pro (early 2011)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP8_TEMPS, ASMC_MBP8_TEMPNAMES, ASMC_MBP8_TEMPDESCS }, { "MacBookPro11,2", "Apple SMC MacBook Pro Retina Core i7 (2013/2014)", ASMC_SMS_FUNCS_DISABLED, ASMC_FAN_FUNCS2, ASMC_LIGHT_FUNCS, ASMC_MBP112_TEMPS, ASMC_MBP112_TEMPNAMES, ASMC_MBP112_TEMPDESCS }, { "MacBookPro11,3", "Apple SMC MacBook Pro Retina Core i7 (2013/2014)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, ASMC_LIGHT_FUNCS, ASMC_MBP113_TEMPS, ASMC_MBP113_TEMPNAMES, ASMC_MBP113_TEMPDESCS }, /* The Mac Mini has no SMS */ { "Macmini1,1", "Apple SMC Mac Mini", NULL, NULL, NULL, ASMC_FAN_FUNCS, NULL, NULL, NULL, ASMC_MM_TEMPS, ASMC_MM_TEMPNAMES, ASMC_MM_TEMPDESCS }, /* The Mac Mini 3,1 has no SMS */ { "Macmini3,1", "Apple SMC Mac Mini 3,1", NULL, NULL, NULL, ASMC_FAN_FUNCS, NULL, NULL, NULL, ASMC_MM31_TEMPS, ASMC_MM31_TEMPNAMES, ASMC_MM31_TEMPDESCS }, /* Idem for the MacPro */ { "MacPro2", "Apple SMC Mac Pro (8-core)", NULL, NULL, NULL, ASMC_FAN_FUNCS, NULL, NULL, NULL, ASMC_MP_TEMPS, ASMC_MP_TEMPNAMES, ASMC_MP_TEMPDESCS }, /* Idem for the MacPro 2010*/ { "MacPro5,1", "Apple SMC MacPro (2010)", NULL, NULL, NULL, ASMC_FAN_FUNCS, NULL, NULL, NULL, ASMC_MP5_TEMPS, ASMC_MP5_TEMPNAMES, ASMC_MP5_TEMPDESCS }, { "MacBookAir1,1", "Apple SMC MacBook Air", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, NULL, NULL, NULL, ASMC_MBA_TEMPS, ASMC_MBA_TEMPNAMES, ASMC_MBA_TEMPDESCS }, { "MacBookAir3,1", "Apple SMC MacBook Air Core 2 Duo (Late 2010)", ASMC_SMS_FUNCS, ASMC_FAN_FUNCS, NULL, NULL, NULL, ASMC_MBA3_TEMPS, ASMC_MBA3_TEMPNAMES, ASMC_MBA3_TEMPDESCS }, { "MacBookAir5,1", "Apple SMC MacBook Air 11-inch (Mid 2012)", ASMC_SMS_FUNCS_DISABLED, ASMC_FAN_FUNCS2, ASMC_LIGHT_FUNCS, ASMC_MBA5_TEMPS, ASMC_MBA5_TEMPNAMES, ASMC_MBA5_TEMPDESCS }, { "MacBookAir5,2", "Apple SMC MacBook Air 13-inch (Mid 2012)", ASMC_SMS_FUNCS_DISABLED, ASMC_FAN_FUNCS2, ASMC_LIGHT_FUNCS, ASMC_MBA5_TEMPS, ASMC_MBA5_TEMPNAMES, ASMC_MBA5_TEMPDESCS }, { NULL, NULL } }; #undef ASMC_SMS_FUNCS #undef ASMC_SMS_FUNCS_DISABLED #undef ASMC_FAN_FUNCS #undef ASMC_FAN_FUNCS2 #undef ASMC_LIGHT_FUNCS /* * Driver methods. */ static device_method_t asmc_methods[] = { DEVMETHOD(device_probe, asmc_probe), DEVMETHOD(device_attach, asmc_attach), DEVMETHOD(device_detach, asmc_detach), DEVMETHOD(device_resume, asmc_resume), { 0, 0 } }; static driver_t asmc_driver = { "asmc", asmc_methods, sizeof(struct asmc_softc) }; /* * Debugging */ #define _COMPONENT ACPI_OEM ACPI_MODULE_NAME("ASMC") #ifdef DEBUG #define ASMC_DPRINTF(str) device_printf(dev, str) #else #define ASMC_DPRINTF(str) #endif /* NB: can't be const */ static char *asmc_ids[] = { "APP0001", NULL }; static devclass_t asmc_devclass; static unsigned int light_control = 0; DRIVER_MODULE(asmc, acpi, asmc_driver, asmc_devclass, NULL, NULL); MODULE_DEPEND(asmc, acpi, 1, 1, 1); static struct asmc_model * asmc_match(device_t dev) { int i; char *model; model = kern_getenv("smbios.system.product"); if (model == NULL) return (NULL); for (i = 0; asmc_models[i].smc_model; i++) { if (!strncmp(model, asmc_models[i].smc_model, strlen(model))) { freeenv(model); return (&asmc_models[i]); } } freeenv(model); return (NULL); } static int asmc_probe(device_t dev) { struct asmc_model *model; + int rv; if (resource_disabled("asmc", 0)) return (ENXIO); - if (ACPI_ID_PROBE(device_get_parent(dev), dev, asmc_ids) == NULL) - return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, asmc_ids, NULL); + if (rv > 0) + return (rv); model = asmc_match(dev); if (!model) { device_printf(dev, "model not recognized\n"); return (ENXIO); } device_set_desc(dev, model->smc_desc); - return (BUS_PROBE_DEFAULT); + return (rv); } static int asmc_attach(device_t dev) { int i, j; int ret; char name[2]; struct asmc_softc *sc = device_get_softc(dev); struct sysctl_ctx_list *sysctlctx; struct sysctl_oid *sysctlnode; struct asmc_model *model; sc->sc_ioport = bus_alloc_resource_any(dev, SYS_RES_IOPORT, &sc->sc_rid_port, RF_ACTIVE); if (sc->sc_ioport == NULL) { device_printf(dev, "unable to allocate IO port\n"); return (ENOMEM); } sysctlctx = device_get_sysctl_ctx(dev); sysctlnode = device_get_sysctl_tree(dev); model = asmc_match(dev); mtx_init(&sc->sc_mtx, "asmc", NULL, MTX_SPIN); sc->sc_model = model; asmc_init(dev); /* * dev.asmc.n.fan.* tree. */ sc->sc_fan_tree[0] = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "fan", CTLFLAG_RD, 0, "Fan Root Tree"); for (i = 1; i <= sc->sc_nfan; i++) { j = i - 1; name[0] = '0' + j; name[1] = 0; sc->sc_fan_tree[i] = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sc->sc_fan_tree[0]), OID_AUTO, name, CTLFLAG_RD, 0, "Fan Subtree"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_fan_tree[i]), OID_AUTO, "id", CTLTYPE_STRING | CTLFLAG_RD, dev, j, model->smc_fan_id, "I", "Fan ID"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_fan_tree[i]), OID_AUTO, "speed", CTLTYPE_INT | CTLFLAG_RD, dev, j, model->smc_fan_speed, "I", "Fan speed in RPM"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_fan_tree[i]), OID_AUTO, "safespeed", CTLTYPE_INT | CTLFLAG_RD, dev, j, model->smc_fan_safespeed, "I", "Fan safe speed in RPM"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_fan_tree[i]), OID_AUTO, "minspeed", CTLTYPE_INT | CTLFLAG_RW, dev, j, model->smc_fan_minspeed, "I", "Fan minimum speed in RPM"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_fan_tree[i]), OID_AUTO, "maxspeed", CTLTYPE_INT | CTLFLAG_RW, dev, j, model->smc_fan_maxspeed, "I", "Fan maximum speed in RPM"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_fan_tree[i]), OID_AUTO, "targetspeed", CTLTYPE_INT | CTLFLAG_RW, dev, j, model->smc_fan_targetspeed, "I", "Fan target speed in RPM"); } /* * dev.asmc.n.temp tree. */ sc->sc_temp_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "temp", CTLFLAG_RD, 0, "Temperature sensors"); for (i = 0; model->smc_temps[i]; i++) { SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_temp_tree), OID_AUTO, model->smc_tempnames[i], CTLTYPE_INT | CTLFLAG_RD, dev, i, asmc_temp_sysctl, "I", model->smc_tempdescs[i]); } /* * dev.asmc.n.light */ if (model->smc_light_left) { sc->sc_light_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "light", CTLFLAG_RD, 0, "Keyboard backlight sensors"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_light_tree), OID_AUTO, "left", CTLTYPE_INT | CTLFLAG_RD, dev, 0, model->smc_light_left, "I", "Keyboard backlight left sensor"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_light_tree), OID_AUTO, "right", CTLTYPE_INT | CTLFLAG_RD, dev, 0, model->smc_light_right, "I", "Keyboard backlight right sensor"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_light_tree), OID_AUTO, "control", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, dev, 0, model->smc_light_control, "I", "Keyboard backlight brightness control"); } if (model->smc_sms_x == NULL) goto nosms; /* * dev.asmc.n.sms tree. */ sc->sc_sms_tree = SYSCTL_ADD_NODE(sysctlctx, SYSCTL_CHILDREN(sysctlnode), OID_AUTO, "sms", CTLFLAG_RD, 0, "Sudden Motion Sensor"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_sms_tree), OID_AUTO, "x", CTLTYPE_INT | CTLFLAG_RD, dev, 0, model->smc_sms_x, "I", "Sudden Motion Sensor X value"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_sms_tree), OID_AUTO, "y", CTLTYPE_INT | CTLFLAG_RD, dev, 0, model->smc_sms_y, "I", "Sudden Motion Sensor Y value"); SYSCTL_ADD_PROC(sysctlctx, SYSCTL_CHILDREN(sc->sc_sms_tree), OID_AUTO, "z", CTLTYPE_INT | CTLFLAG_RD, dev, 0, model->smc_sms_z, "I", "Sudden Motion Sensor Z value"); /* * Need a taskqueue to send devctl_notify() events * when the SMS interrupt us. * * PI_REALTIME is used due to the sensitivity of the * interrupt. An interrupt from the SMS means that the * disk heads should be turned off as quickly as possible. * * We only need to do this for the non INTR_FILTER case. */ sc->sc_sms_tq = NULL; TASK_INIT(&sc->sc_sms_task, 0, asmc_sms_task, sc); sc->sc_sms_tq = taskqueue_create_fast("asmc_taskq", M_WAITOK, taskqueue_thread_enqueue, &sc->sc_sms_tq); taskqueue_start_threads(&sc->sc_sms_tq, 1, PI_REALTIME, "%s sms taskq", device_get_nameunit(dev)); /* * Allocate an IRQ for the SMS. */ sc->sc_rid_irq = 0; sc->sc_irq = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_rid_irq, RF_ACTIVE); if (sc->sc_irq == NULL) { device_printf(dev, "unable to allocate IRQ resource\n"); ret = ENXIO; goto err2; } ret = bus_setup_intr(dev, sc->sc_irq, INTR_TYPE_MISC | INTR_MPSAFE, asmc_sms_intrfast, NULL, dev, &sc->sc_cookie); if (ret) { device_printf(dev, "unable to setup SMS IRQ\n"); goto err1; } nosms: return (0); err1: bus_release_resource(dev, SYS_RES_IRQ, sc->sc_rid_irq, sc->sc_irq); err2: bus_release_resource(dev, SYS_RES_IOPORT, sc->sc_rid_port, sc->sc_ioport); mtx_destroy(&sc->sc_mtx); if (sc->sc_sms_tq) taskqueue_free(sc->sc_sms_tq); return (ret); } static int asmc_detach(device_t dev) { struct asmc_softc *sc = device_get_softc(dev); if (sc->sc_sms_tq) { taskqueue_drain(sc->sc_sms_tq, &sc->sc_sms_task); taskqueue_free(sc->sc_sms_tq); } if (sc->sc_cookie) bus_teardown_intr(dev, sc->sc_irq, sc->sc_cookie); if (sc->sc_irq) bus_release_resource(dev, SYS_RES_IRQ, sc->sc_rid_irq, sc->sc_irq); if (sc->sc_ioport) bus_release_resource(dev, SYS_RES_IOPORT, sc->sc_rid_port, sc->sc_ioport); mtx_destroy(&sc->sc_mtx); return (0); } static int asmc_resume(device_t dev) { uint8_t buf[2]; buf[0] = light_control; buf[1] = 0x00; asmc_key_write(dev, ASMC_KEY_LIGHTVALUE, buf, sizeof buf); return (0); } #ifdef DEBUG void asmc_dumpall(device_t dev) { int i; /* XXX magic number */ for (i=0; i < 0x100; i++) asmc_key_dump(dev, i); } #endif static int asmc_init(device_t dev) { struct asmc_softc *sc = device_get_softc(dev); int i, error = 1; uint8_t buf[4]; if (sc->sc_model->smc_sms_x == NULL) goto nosms; /* * We are ready to receive interrupts from the SMS. */ buf[0] = 0x01; ASMC_DPRINTF(("intok key\n")); asmc_key_write(dev, ASMC_KEY_INTOK, buf, 1); DELAY(50); /* * Initiate the polling intervals. */ buf[0] = 20; /* msecs */ ASMC_DPRINTF(("low int key\n")); asmc_key_write(dev, ASMC_KEY_SMS_LOW_INT, buf, 1); DELAY(200); buf[0] = 20; /* msecs */ ASMC_DPRINTF(("high int key\n")); asmc_key_write(dev, ASMC_KEY_SMS_HIGH_INT, buf, 1); DELAY(200); buf[0] = 0x00; buf[1] = 0x60; ASMC_DPRINTF(("sms low key\n")); asmc_key_write(dev, ASMC_KEY_SMS_LOW, buf, 2); DELAY(200); buf[0] = 0x01; buf[1] = 0xc0; ASMC_DPRINTF(("sms high key\n")); asmc_key_write(dev, ASMC_KEY_SMS_HIGH, buf, 2); DELAY(200); /* * I'm not sure what this key does, but it seems to be * required. */ buf[0] = 0x01; ASMC_DPRINTF(("sms flag key\n")); asmc_key_write(dev, ASMC_KEY_SMS_FLAG, buf, 1); DELAY(100); sc->sc_sms_intr_works = 0; /* * Retry SMS initialization 1000 times * (takes approx. 2 seconds in worst case) */ for (i = 0; i < 1000; i++) { if (asmc_key_read(dev, ASMC_KEY_SMS, buf, 2) == 0 && (buf[0] == ASMC_SMS_INIT1 && buf[1] == ASMC_SMS_INIT2)) { error = 0; sc->sc_sms_intr_works = 1; goto out; } buf[0] = ASMC_SMS_INIT1; buf[1] = ASMC_SMS_INIT2; ASMC_DPRINTF(("sms key\n")); asmc_key_write(dev, ASMC_KEY_SMS, buf, 2); DELAY(50); } device_printf(dev, "WARNING: Sudden Motion Sensor not initialized!\n"); out: asmc_sms_calibrate(dev); nosms: sc->sc_nfan = asmc_fan_count(dev); if (sc->sc_nfan > ASMC_MAXFANS) { device_printf(dev, "more than %d fans were detected. Please " "report this.\n", ASMC_MAXFANS); sc->sc_nfan = ASMC_MAXFANS; } if (bootverbose) { /* * The number of keys is a 32 bit buffer */ asmc_key_read(dev, ASMC_NKEYS, buf, 4); device_printf(dev, "number of keys: %d\n", ntohl(*(uint32_t*)buf)); } #ifdef DEBUG asmc_dumpall(dev); #endif return (error); } /* * We need to make sure that the SMC acks the byte sent. * Just wait up to (amount * 10) ms. */ static int asmc_wait_ack(device_t dev, uint8_t val, int amount) { struct asmc_softc *sc = device_get_softc(dev); u_int i; val = val & ASMC_STATUS_MASK; for (i = 0; i < amount; i++) { if ((ASMC_CMDPORT_READ(sc) & ASMC_STATUS_MASK) == val) return (0); DELAY(10); } return (1); } /* * We need to make sure that the SMC acks the byte sent. * Just wait up to 100 ms. */ static int asmc_wait(device_t dev, uint8_t val) { struct asmc_softc *sc; if (asmc_wait_ack(dev, val, 1000) == 0) return (0); sc = device_get_softc(dev); val = val & ASMC_STATUS_MASK; #ifdef DEBUG device_printf(dev, "%s failed: 0x%x, 0x%x\n", __func__, val, ASMC_CMDPORT_READ(sc)); #endif return (1); } /* * Send the given command, retrying up to 10 times if * the acknowledgement fails. */ static int asmc_command(device_t dev, uint8_t command) { int i; struct asmc_softc *sc = device_get_softc(dev); for (i=0; i < 10; i++) { ASMC_CMDPORT_WRITE(sc, command); if (asmc_wait_ack(dev, 0x0c, 100) == 0) { return (0); } } #ifdef DEBUG device_printf(dev, "%s failed: 0x%x, 0x%x\n", __func__, command, ASMC_CMDPORT_READ(sc)); #endif return (1); } static int asmc_key_read(device_t dev, const char *key, uint8_t *buf, uint8_t len) { int i, error = 1, try = 0; struct asmc_softc *sc = device_get_softc(dev); mtx_lock_spin(&sc->sc_mtx); begin: if (asmc_command(dev, ASMC_CMDREAD)) goto out; for (i = 0; i < 4; i++) { ASMC_DATAPORT_WRITE(sc, key[i]); if (asmc_wait(dev, 0x04)) goto out; } ASMC_DATAPORT_WRITE(sc, len); for (i = 0; i < len; i++) { if (asmc_wait(dev, 0x05)) goto out; buf[i] = ASMC_DATAPORT_READ(sc); } error = 0; out: if (error) { if (++try < 10) goto begin; device_printf(dev,"%s for key %s failed %d times, giving up\n", __func__, key, try); } mtx_unlock_spin(&sc->sc_mtx); return (error); } #ifdef DEBUG static int asmc_key_dump(device_t dev, int number) { struct asmc_softc *sc = device_get_softc(dev); char key[5] = { 0 }; char type[7] = { 0 }; uint8_t index[4]; uint8_t v[32]; uint8_t maxlen; int i, error = 1, try = 0; mtx_lock_spin(&sc->sc_mtx); index[0] = (number >> 24) & 0xff; index[1] = (number >> 16) & 0xff; index[2] = (number >> 8) & 0xff; index[3] = (number) & 0xff; begin: if (asmc_command(dev, 0x12)) goto out; for (i = 0; i < 4; i++) { ASMC_DATAPORT_WRITE(sc, index[i]); if (asmc_wait(dev, 0x04)) goto out; } ASMC_DATAPORT_WRITE(sc, 4); for (i = 0; i < 4; i++) { if (asmc_wait(dev, 0x05)) goto out; key[i] = ASMC_DATAPORT_READ(sc); } /* get type */ if (asmc_command(dev, 0x13)) goto out; for (i = 0; i < 4; i++) { ASMC_DATAPORT_WRITE(sc, key[i]); if (asmc_wait(dev, 0x04)) goto out; } ASMC_DATAPORT_WRITE(sc, 6); for (i = 0; i < 6; i++) { if (asmc_wait(dev, 0x05)) goto out; type[i] = ASMC_DATAPORT_READ(sc); } error = 0; out: if (error) { if (++try < 10) goto begin; device_printf(dev,"%s for key %s failed %d times, giving up\n", __func__, key, try); mtx_unlock_spin(&sc->sc_mtx); } else { char buf[1024]; char buf2[8]; mtx_unlock_spin(&sc->sc_mtx); maxlen = type[0]; type[0] = ' '; type[5] = 0; if (maxlen > sizeof(v)) { device_printf(dev, "WARNING: cropping maxlen from %d to %zu\n", maxlen, sizeof(v)); maxlen = sizeof(v); } for (i = 0; i < sizeof(v); i++) { v[i] = 0; } asmc_key_read(dev, key, v, maxlen); snprintf(buf, sizeof(buf), "key %d is: %s, type %s " "(len %d), data", number, key, type, maxlen); for (i = 0; i < maxlen; i++) { snprintf(buf2, sizeof(buf2), " %02x", v[i]); strlcat(buf, buf2, sizeof(buf)); } strlcat(buf, " \n", sizeof(buf)); device_printf(dev, "%s", buf); } return (error); } #endif static int asmc_key_write(device_t dev, const char *key, uint8_t *buf, uint8_t len) { int i, error = -1, try = 0; struct asmc_softc *sc = device_get_softc(dev); mtx_lock_spin(&sc->sc_mtx); begin: ASMC_DPRINTF(("cmd port: cmd write\n")); if (asmc_command(dev, ASMC_CMDWRITE)) goto out; ASMC_DPRINTF(("data port: key\n")); for (i = 0; i < 4; i++) { ASMC_DATAPORT_WRITE(sc, key[i]); if (asmc_wait(dev, 0x04)) goto out; } ASMC_DPRINTF(("data port: length\n")); ASMC_DATAPORT_WRITE(sc, len); ASMC_DPRINTF(("data port: buffer\n")); for (i = 0; i < len; i++) { if (asmc_wait(dev, 0x04)) goto out; ASMC_DATAPORT_WRITE(sc, buf[i]); } error = 0; out: if (error) { if (++try < 10) goto begin; device_printf(dev,"%s for key %s failed %d times, giving up\n", __func__, key, try); } mtx_unlock_spin(&sc->sc_mtx); return (error); } /* * Fan control functions. */ static int asmc_fan_count(device_t dev) { uint8_t buf[1]; if (asmc_key_read(dev, ASMC_KEY_FANCOUNT, buf, sizeof buf) < 0) return (-1); return (buf[0]); } static int asmc_fan_getvalue(device_t dev, const char *key, int fan) { int speed; uint8_t buf[2]; char fankey[5]; snprintf(fankey, sizeof(fankey), key, fan); if (asmc_key_read(dev, fankey, buf, sizeof buf) < 0) return (-1); speed = (buf[0] << 6) | (buf[1] >> 2); return (speed); } static char* asmc_fan_getstring(device_t dev, const char *key, int fan, uint8_t *buf, uint8_t buflen) { char fankey[5]; char* desc; snprintf(fankey, sizeof(fankey), key, fan); if (asmc_key_read(dev, fankey, buf, buflen) < 0) return (NULL); desc = buf+4; return (desc); } static int asmc_fan_setvalue(device_t dev, const char *key, int fan, int speed) { uint8_t buf[2]; char fankey[5]; speed *= 4; buf[0] = speed>>8; buf[1] = speed; snprintf(fankey, sizeof(fankey), key, fan); if (asmc_key_write(dev, fankey, buf, sizeof buf) < 0) return (-1); return (0); } static int asmc_mb_sysctl_fanspeed(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; int fan = arg2; int error; int32_t v; v = asmc_fan_getvalue(dev, ASMC_KEY_FANSPEED, fan); error = sysctl_handle_int(oidp, &v, 0, req); return (error); } static int asmc_mb_sysctl_fanid(SYSCTL_HANDLER_ARGS) { uint8_t buf[16]; device_t dev = (device_t) arg1; int fan = arg2; int error = true; char* desc; desc = asmc_fan_getstring(dev, ASMC_KEY_FANID, fan, buf, sizeof(buf)); if (desc != NULL) error = sysctl_handle_string(oidp, desc, 0, req); return (error); } static int asmc_mb_sysctl_fansafespeed(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; int fan = arg2; int error; int32_t v; v = asmc_fan_getvalue(dev, ASMC_KEY_FANSAFESPEED, fan); error = sysctl_handle_int(oidp, &v, 0, req); return (error); } static int asmc_mb_sysctl_fanminspeed(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; int fan = arg2; int error; int32_t v; v = asmc_fan_getvalue(dev, ASMC_KEY_FANMINSPEED, fan); error = sysctl_handle_int(oidp, &v, 0, req); if (error == 0 && req->newptr != NULL) { unsigned int newspeed = v; asmc_fan_setvalue(dev, ASMC_KEY_FANMINSPEED, fan, newspeed); } return (error); } static int asmc_mb_sysctl_fanmaxspeed(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; int fan = arg2; int error; int32_t v; v = asmc_fan_getvalue(dev, ASMC_KEY_FANMAXSPEED, fan); error = sysctl_handle_int(oidp, &v, 0, req); if (error == 0 && req->newptr != NULL) { unsigned int newspeed = v; asmc_fan_setvalue(dev, ASMC_KEY_FANMAXSPEED, fan, newspeed); } return (error); } static int asmc_mb_sysctl_fantargetspeed(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; int fan = arg2; int error; int32_t v; v = asmc_fan_getvalue(dev, ASMC_KEY_FANTARGETSPEED, fan); error = sysctl_handle_int(oidp, &v, 0, req); if (error == 0 && req->newptr != NULL) { unsigned int newspeed = v; asmc_fan_setvalue(dev, ASMC_KEY_FANTARGETSPEED, fan, newspeed); } return (error); } /* * Temperature functions. */ static int asmc_temp_getvalue(device_t dev, const char *key) { uint8_t buf[2]; /* * Check for invalid temperatures. */ if (asmc_key_read(dev, key, buf, sizeof buf) < 0) return (-1); return (buf[0]); } static int asmc_temp_sysctl(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; struct asmc_softc *sc = device_get_softc(dev); int error, val; val = asmc_temp_getvalue(dev, sc->sc_model->smc_temps[arg2]); error = sysctl_handle_int(oidp, &val, 0, req); return (error); } /* * Sudden Motion Sensor functions. */ static int asmc_sms_read(device_t dev, const char *key, int16_t *val) { uint8_t buf[2]; int error; /* no need to do locking here as asmc_key_read() already does it */ switch (key[3]) { case 'X': case 'Y': case 'Z': error = asmc_key_read(dev, key, buf, sizeof buf); break; default: device_printf(dev, "%s called with invalid argument %s\n", __func__, key); error = 1; goto out; } *val = ((int16_t)buf[0] << 8) | buf[1]; out: return (error); } static void asmc_sms_calibrate(device_t dev) { struct asmc_softc *sc = device_get_softc(dev); asmc_sms_read(dev, ASMC_KEY_SMS_X, &sc->sms_rest_x); asmc_sms_read(dev, ASMC_KEY_SMS_Y, &sc->sms_rest_y); asmc_sms_read(dev, ASMC_KEY_SMS_Z, &sc->sms_rest_z); } static int asmc_sms_intrfast(void *arg) { uint8_t type; device_t dev = (device_t) arg; struct asmc_softc *sc = device_get_softc(dev); if (!sc->sc_sms_intr_works) return (FILTER_HANDLED); mtx_lock_spin(&sc->sc_mtx); type = ASMC_INTPORT_READ(sc); mtx_unlock_spin(&sc->sc_mtx); sc->sc_sms_intrtype = type; asmc_sms_printintr(dev, type); taskqueue_enqueue(sc->sc_sms_tq, &sc->sc_sms_task); return (FILTER_HANDLED); } static void asmc_sms_printintr(device_t dev, uint8_t type) { switch (type) { case ASMC_SMS_INTFF: device_printf(dev, "WARNING: possible free fall!\n"); break; case ASMC_SMS_INTHA: device_printf(dev, "WARNING: high acceleration detected!\n"); break; case ASMC_SMS_INTSH: device_printf(dev, "WARNING: possible shock!\n"); break; default: device_printf(dev, "%s unknown interrupt\n", __func__); } } static void asmc_sms_task(void *arg, int pending) { struct asmc_softc *sc = (struct asmc_softc *)arg; char notify[16]; int type; switch (sc->sc_sms_intrtype) { case ASMC_SMS_INTFF: type = 2; break; case ASMC_SMS_INTHA: type = 1; break; case ASMC_SMS_INTSH: type = 0; break; default: type = 255; } snprintf(notify, sizeof(notify), " notify=0x%x", type); devctl_notify("ACPI", "asmc", "SMS", notify); } static int asmc_mb_sysctl_sms_x(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; int error; int16_t val; int32_t v; asmc_sms_read(dev, ASMC_KEY_SMS_X, &val); v = (int32_t) val; error = sysctl_handle_int(oidp, &v, 0, req); return (error); } static int asmc_mb_sysctl_sms_y(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; int error; int16_t val; int32_t v; asmc_sms_read(dev, ASMC_KEY_SMS_Y, &val); v = (int32_t) val; error = sysctl_handle_int(oidp, &v, 0, req); return (error); } static int asmc_mb_sysctl_sms_z(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; int error; int16_t val; int32_t v; asmc_sms_read(dev, ASMC_KEY_SMS_Z, &val); v = (int32_t) val; error = sysctl_handle_int(oidp, &v, 0, req); return (error); } static int asmc_mbp_sysctl_light_left(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; uint8_t buf[6]; int error; int32_t v; asmc_key_read(dev, ASMC_KEY_LIGHTLEFT, buf, sizeof buf); v = buf[2]; error = sysctl_handle_int(oidp, &v, 0, req); return (error); } static int asmc_mbp_sysctl_light_right(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; uint8_t buf[6]; int error; int32_t v; asmc_key_read(dev, ASMC_KEY_LIGHTRIGHT, buf, sizeof buf); v = buf[2]; error = sysctl_handle_int(oidp, &v, 0, req); return (error); } static int asmc_mbp_sysctl_light_control(SYSCTL_HANDLER_ARGS) { device_t dev = (device_t) arg1; uint8_t buf[2]; int error; int v; v = light_control; error = sysctl_handle_int(oidp, &v, 0, req); if (error == 0 && req->newptr != NULL) { if (v < 0 || v > 255) return (EINVAL); light_control = v; buf[0] = light_control; buf[1] = 0x00; asmc_key_write(dev, ASMC_KEY_LIGHTVALUE, buf, sizeof buf); } return (error); } Index: head/sys/dev/fdc/fdc_acpi.c =================================================================== --- head/sys/dev/fdc/fdc_acpi.c (revision 339753) +++ head/sys/dev/fdc/fdc_acpi.c (revision 339754) @@ -1,274 +1,276 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2004 Nate Lawson (SDG) * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include static int fdc_acpi_probe(device_t dev); static int fdc_acpi_attach(device_t dev); static int fdc_acpi_probe_children(device_t bus, device_t dev, void *fde); static ACPI_STATUS fdc_acpi_probe_child(ACPI_HANDLE h, device_t *dev, int level, void *arg); /* Maximum number of child devices of a controller (4 floppy + 1 tape.) */ #define ACPI_FDC_MAXDEVS 5 /* Standard size of buffer returned by the _FDE method. */ #define ACPI_FDC_FDE_LEN (ACPI_FDC_MAXDEVS * sizeof(uint32_t)) /* * Parameters for the tape drive (5th device). Some BIOS authors use this * for all drives, not just the tape drive (e.g., ASUS K8V). This isn't * grossly incompatible with the spec since it says the first four devices * are simple booleans. */ #define ACPI_FD_UNKNOWN 0 #define ACPI_FD_PRESENT 1 #define ACPI_FD_NEVER_PRESENT 2 /* Temporary buf length for evaluating _FDE and _FDI. */ #define ACPI_FDC_BUFLEN 1024 /* Context for walking FDC child devices. */ struct fdc_walk_ctx { uint32_t fd_present[ACPI_FDC_MAXDEVS]; int index; device_t acpi_dev; device_t dev; }; static int fdc_acpi_probe(device_t dev) { device_t bus; static char *fdc_ids[] = { "PNP0700", "PNP0701", NULL }; + int rv; bus = device_get_parent(dev); - if (ACPI_ID_PROBE(bus, dev, fdc_ids) == NULL) - return (ENXIO); + rv = ACPI_ID_PROBE(bus, dev, fdc_ids, NULL); + if (rv > 0) + return (rv); if (ACPI_SUCCESS(ACPI_EVALUATE_OBJECT(bus, dev, "_FDE", NULL, NULL))) device_set_desc(dev, "floppy drive controller (FDE)"); else device_set_desc(dev, "floppy drive controller"); - return (0); + return (rv); } static int fdc_acpi_attach(device_t dev) { struct fdc_data *sc; ACPI_BUFFER buf; ACPI_OBJECT *obj; device_t bus; int error; /* Get our softc and use the same accessor as ISA. */ sc = device_get_softc(dev); sc->fdc_dev = dev; /* Initialize variables and get a temporary buffer for _FDE. */ error = ENXIO; buf.Length = ACPI_FDC_BUFLEN; buf.Pointer = malloc(buf.Length, M_TEMP, M_NOWAIT | M_ZERO); if (buf.Pointer == NULL) goto out; /* Allocate resources the same as the ISA attachment. */ error = fdc_isa_alloc_resources(dev, sc); if (error != 0) goto out; /* Call common attach code in fdc(4) first. */ error = fdc_attach(dev); if (error != 0) goto out; /* * Enumerate _FDE, which lists floppy drives that are present. If * this fails, fall back to the ISA hints-based probe method. */ bus = device_get_parent(dev); if (ACPI_FAILURE(ACPI_EVALUATE_OBJECT(bus, dev, "_FDE", NULL, &buf))) { error = fdc_hints_probe(dev); goto out; } /* Add fd child devices as specified. */ obj = buf.Pointer; error = fdc_acpi_probe_children(bus, dev, obj->Buffer.Pointer); out: if (buf.Pointer) free(buf.Pointer, M_TEMP); if (error != 0) fdc_release_resources(sc); else fdc_start_worker(dev); return (error); } static int fdc_acpi_probe_children(device_t bus, device_t dev, void *fde) { struct fdc_walk_ctx *ctx; devclass_t fd_dc; int i; /* Setup the context and walk all child devices. */ ctx = malloc(sizeof(struct fdc_walk_ctx), M_TEMP, M_NOWAIT); if (ctx == NULL) { device_printf(dev, "no memory for walking children\n"); return (ENOMEM); } bcopy(fde, ctx->fd_present, sizeof(ctx->fd_present)); ctx->index = 0; ctx->dev = dev; ctx->acpi_dev = bus; ACPI_SCAN_CHILDREN(ctx->acpi_dev, dev, 1, fdc_acpi_probe_child, ctx); /* Add any devices not represented by an AML Device handle/node. */ fd_dc = devclass_find("fd"); for (i = 0; i < ACPI_FDC_MAXDEVS; i++) if (ctx->fd_present[i] == ACPI_FD_PRESENT && devclass_get_device(fd_dc, i) == NULL) { if (fdc_add_child(dev, "fd", i) == NULL) device_printf(dev, "fd add failed\n"); } free(ctx, M_TEMP); /* Attach any children found during the probe. */ return (bus_generic_attach(dev)); } static ACPI_STATUS fdc_acpi_probe_child(ACPI_HANDLE h, device_t *dev, int level, void *arg) { struct fdc_walk_ctx *ctx; device_t child, old_child; ACPI_BUFFER buf; ACPI_OBJECT *pkg, *obj; ACPI_STATUS status; ctx = (struct fdc_walk_ctx *)arg; buf.Pointer = NULL; /* * The first four ints are booleans that indicate whether fd0-3 are * present or not. The last is for a tape device, which we don't * bother supporting for now. */ if (ctx->index > 3) return (AE_OK); /* This device is not present, move on to the next. */ if (ctx->fd_present[ctx->index] != ACPI_FD_PRESENT) goto out; /* Create a device for the child with the given index. */ child = fdc_add_child(ctx->dev, "fd", ctx->index); if (child == NULL) goto out; old_child = *dev; *dev = child; /* Get temporary buffer for _FDI probe. */ buf.Length = ACPI_FDC_BUFLEN; buf.Pointer = malloc(buf.Length, M_TEMP, M_NOWAIT | M_ZERO); if (buf.Pointer == NULL) goto out; /* * Evaluate _FDI to get drive type to pass to the child. We use the * old child here since it has a valid ACPI_HANDLE since it is a * child of acpi. A better way to implement this would be to make fdc * support the ACPI handle ivar for its children. */ status = ACPI_EVALUATE_OBJECT(ctx->acpi_dev, old_child, "_FDI", NULL, &buf); if (ACPI_FAILURE(status)) { if (status != AE_NOT_FOUND) device_printf(ctx->dev, "_FDI failed - %#x\n", status); goto out; } pkg = (ACPI_OBJECT *)buf.Pointer; if (!ACPI_PKG_VALID(pkg, 16)) { device_printf(ctx->dev, "invalid _FDI package\n"); goto out; } obj = &pkg->Package.Elements[1]; if (obj == NULL || obj->Type != ACPI_TYPE_INTEGER) { device_printf(ctx->dev, "invalid type object in _FDI\n"); goto out; } fdc_set_fdtype(child, obj->Integer.Value); out: ctx->index++; if (buf.Pointer) free(buf.Pointer, M_TEMP); return (AE_OK); } static device_method_t fdc_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, fdc_acpi_probe), DEVMETHOD(device_attach, fdc_acpi_attach), DEVMETHOD(device_detach, fdc_detach), /* Bus interface */ DEVMETHOD(bus_print_child, fdc_print_child), DEVMETHOD(bus_read_ivar, fdc_read_ivar), DEVMETHOD(bus_write_ivar, fdc_write_ivar), DEVMETHOD_END }; static driver_t fdc_acpi_driver = { "fdc", fdc_acpi_methods, sizeof(struct fdc_data) }; DRIVER_MODULE(fdc, acpi, fdc_acpi_driver, fdc_devclass, 0, 0); Index: head/sys/dev/gpio/bytgpio.c =================================================================== --- head/sys/dev/gpio/bytgpio.c (revision 339753) +++ head/sys/dev/gpio/bytgpio.c (revision 339754) @@ -1,679 +1,680 @@ /*- * Copyright (c) 2016 Oleksandr Tymoshenko * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "gpio_if.h" /** * Macros for driver mutex locking */ #define BYTGPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define BYTGPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define BYTGPIO_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ "bytgpio", MTX_SPIN) #define BYTGPIO_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) #define BYTGPIO_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define BYTGPIO_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED) struct pinmap_info { int reg; int pad_func; }; /* Ignore function check, no info is available at the moment */ #define PADCONF_FUNC_ANY -1 #define GPIO_PIN_MAP(r, f) { .reg = (r), .pad_func = (f) } struct bytgpio_softc { ACPI_HANDLE sc_handle; device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; int sc_mem_rid; struct resource *sc_mem_res; int sc_npins; const char* sc_bank_prefix; const struct pinmap_info *sc_pinpad_map; /* List of current functions for pads shared by GPIO */ int *sc_pad_funcs; }; static int bytgpio_probe(device_t dev); static int bytgpio_attach(device_t dev); static int bytgpio_detach(device_t dev); #define SCORE_UID 1 #define SCORE_BANK_PREFIX "GPIO_S0_SC" const struct pinmap_info bytgpio_score_pins[] = { GPIO_PIN_MAP(85, 0), GPIO_PIN_MAP(89, 0), GPIO_PIN_MAP(93, 0), GPIO_PIN_MAP(96, 0), GPIO_PIN_MAP(99, 0), GPIO_PIN_MAP(102, 0), GPIO_PIN_MAP(98, 0), GPIO_PIN_MAP(101, 0), GPIO_PIN_MAP(34, 0), GPIO_PIN_MAP(37, 0), GPIO_PIN_MAP(36, 0), GPIO_PIN_MAP(38, 0), GPIO_PIN_MAP(39, 0), GPIO_PIN_MAP(35, 0), GPIO_PIN_MAP(40, 0), GPIO_PIN_MAP(84, 0), GPIO_PIN_MAP(62, 0), GPIO_PIN_MAP(61, 0), GPIO_PIN_MAP(64, 0), GPIO_PIN_MAP(59, 0), GPIO_PIN_MAP(54, 0), GPIO_PIN_MAP(56, 0), GPIO_PIN_MAP(60, 0), GPIO_PIN_MAP(55, 0), GPIO_PIN_MAP(63, 0), GPIO_PIN_MAP(57, 0), GPIO_PIN_MAP(51, 0), GPIO_PIN_MAP(50, 0), GPIO_PIN_MAP(53, 0), GPIO_PIN_MAP(47, 0), GPIO_PIN_MAP(52, 0), GPIO_PIN_MAP(49, 0), GPIO_PIN_MAP(48, 0), GPIO_PIN_MAP(43, 0), GPIO_PIN_MAP(46, 0), GPIO_PIN_MAP(41, 0), GPIO_PIN_MAP(45, 0), GPIO_PIN_MAP(42, 0), GPIO_PIN_MAP(58, 0), GPIO_PIN_MAP(44, 0), GPIO_PIN_MAP(95, 0), GPIO_PIN_MAP(105, 0), GPIO_PIN_MAP(70, 0), GPIO_PIN_MAP(68, 0), GPIO_PIN_MAP(67, 0), GPIO_PIN_MAP(66, 0), GPIO_PIN_MAP(69, 0), GPIO_PIN_MAP(71, 0), GPIO_PIN_MAP(65, 0), GPIO_PIN_MAP(72, 0), GPIO_PIN_MAP(86, 0), GPIO_PIN_MAP(90, 0), GPIO_PIN_MAP(88, 0), GPIO_PIN_MAP(92, 0), GPIO_PIN_MAP(103, 0), GPIO_PIN_MAP(77, 0), GPIO_PIN_MAP(79, 0), GPIO_PIN_MAP(83, 0), GPIO_PIN_MAP(78, 0), GPIO_PIN_MAP(81, 0), GPIO_PIN_MAP(80, 0), GPIO_PIN_MAP(82, 0), GPIO_PIN_MAP(13, 0), GPIO_PIN_MAP(12, 0), GPIO_PIN_MAP(15, 0), GPIO_PIN_MAP(14, 0), GPIO_PIN_MAP(17, 0), GPIO_PIN_MAP(18, 0), GPIO_PIN_MAP(19, 0), GPIO_PIN_MAP(16, 0), GPIO_PIN_MAP(2, 0), GPIO_PIN_MAP(1, 0), GPIO_PIN_MAP(0, 0), GPIO_PIN_MAP(4, 0), GPIO_PIN_MAP(6, 0), GPIO_PIN_MAP(7, 0), GPIO_PIN_MAP(9, 0), GPIO_PIN_MAP(8, 0), GPIO_PIN_MAP(33, 0), GPIO_PIN_MAP(32, 0), GPIO_PIN_MAP(31, 0), GPIO_PIN_MAP(30, 0), GPIO_PIN_MAP(29, 0), GPIO_PIN_MAP(27, 0), GPIO_PIN_MAP(25, 0), GPIO_PIN_MAP(28, 0), GPIO_PIN_MAP(26, 0), GPIO_PIN_MAP(23, 0), GPIO_PIN_MAP(21, 0), GPIO_PIN_MAP(20, 0), GPIO_PIN_MAP(24, 0), GPIO_PIN_MAP(22, 0), GPIO_PIN_MAP(5, 1), GPIO_PIN_MAP(3, 1), GPIO_PIN_MAP(10, 0), GPIO_PIN_MAP(11, 0), GPIO_PIN_MAP(106, 0), GPIO_PIN_MAP(87, 0), GPIO_PIN_MAP(91, 0), GPIO_PIN_MAP(104, 0), GPIO_PIN_MAP(97, 0), GPIO_PIN_MAP(100, 0) }; #define SCORE_PINS nitems(bytgpio_score_pins) #define NCORE_UID 2 #define NCORE_BANK_PREFIX "GPIO_S0_NC" const struct pinmap_info bytgpio_ncore_pins[] = { GPIO_PIN_MAP(19, PADCONF_FUNC_ANY), GPIO_PIN_MAP(18, PADCONF_FUNC_ANY), GPIO_PIN_MAP(17, PADCONF_FUNC_ANY), GPIO_PIN_MAP(20, PADCONF_FUNC_ANY), GPIO_PIN_MAP(21, PADCONF_FUNC_ANY), GPIO_PIN_MAP(22, PADCONF_FUNC_ANY), GPIO_PIN_MAP(24, PADCONF_FUNC_ANY), GPIO_PIN_MAP(25, PADCONF_FUNC_ANY), GPIO_PIN_MAP(23, PADCONF_FUNC_ANY), GPIO_PIN_MAP(16, PADCONF_FUNC_ANY), GPIO_PIN_MAP(14, PADCONF_FUNC_ANY), GPIO_PIN_MAP(15, PADCONF_FUNC_ANY), GPIO_PIN_MAP(12, PADCONF_FUNC_ANY), GPIO_PIN_MAP(26, PADCONF_FUNC_ANY), GPIO_PIN_MAP(27, PADCONF_FUNC_ANY), GPIO_PIN_MAP(1, PADCONF_FUNC_ANY), GPIO_PIN_MAP(4, PADCONF_FUNC_ANY), GPIO_PIN_MAP(8, PADCONF_FUNC_ANY), GPIO_PIN_MAP(11, PADCONF_FUNC_ANY), GPIO_PIN_MAP(0, PADCONF_FUNC_ANY), GPIO_PIN_MAP(3, PADCONF_FUNC_ANY), GPIO_PIN_MAP(6, PADCONF_FUNC_ANY), GPIO_PIN_MAP(10, PADCONF_FUNC_ANY), GPIO_PIN_MAP(13, PADCONF_FUNC_ANY), GPIO_PIN_MAP(2, PADCONF_FUNC_ANY), GPIO_PIN_MAP(5, PADCONF_FUNC_ANY), GPIO_PIN_MAP(9, PADCONF_FUNC_ANY), GPIO_PIN_MAP(7, PADCONF_FUNC_ANY) }; #define NCORE_PINS nitems(bytgpio_ncore_pins) #define SUS_UID 3 #define SUS_BANK_PREFIX "GPIO_S5_" const struct pinmap_info bytgpio_sus_pins[] = { GPIO_PIN_MAP(29, 0), GPIO_PIN_MAP(33, 0), GPIO_PIN_MAP(30, 0), GPIO_PIN_MAP(31, 0), GPIO_PIN_MAP(32, 0), GPIO_PIN_MAP(34, 0), GPIO_PIN_MAP(36, 0), GPIO_PIN_MAP(35, 0), GPIO_PIN_MAP(38, 0), GPIO_PIN_MAP(37, 0), GPIO_PIN_MAP(18, 0), GPIO_PIN_MAP(7, 1), GPIO_PIN_MAP(11, 1), GPIO_PIN_MAP(20, 1), GPIO_PIN_MAP(17, 1), GPIO_PIN_MAP(1, 1), GPIO_PIN_MAP(8, 1), GPIO_PIN_MAP(10, 1), GPIO_PIN_MAP(19, 1), GPIO_PIN_MAP(12, 1), GPIO_PIN_MAP(0, 1), GPIO_PIN_MAP(2, 1), GPIO_PIN_MAP(23, 0), GPIO_PIN_MAP(39, 0), GPIO_PIN_MAP(28, 0), GPIO_PIN_MAP(27, 0), GPIO_PIN_MAP(22, 0), GPIO_PIN_MAP(21, 0), GPIO_PIN_MAP(24, 0), GPIO_PIN_MAP(25, 0), GPIO_PIN_MAP(26, 0), GPIO_PIN_MAP(51, 0), GPIO_PIN_MAP(56, 0), GPIO_PIN_MAP(54, 0), GPIO_PIN_MAP(49, 0), GPIO_PIN_MAP(55, 0), GPIO_PIN_MAP(48, 0), GPIO_PIN_MAP(57, 0), GPIO_PIN_MAP(50, 0), GPIO_PIN_MAP(58, 0), GPIO_PIN_MAP(52, 0), GPIO_PIN_MAP(53, 0), GPIO_PIN_MAP(59, 0), GPIO_PIN_MAP(40, 0) }; #define SUS_PINS nitems(bytgpio_sus_pins) #define BYGPIO_PIN_REGISTER(sc, pin, r) ((sc)->sc_pinpad_map[(pin)].reg * 16 + (r)) #define BYTGPIO_PCONF0 0x0000 #define BYTGPIO_PCONF0_FUNC_MASK 7 #define BYTGPIO_PAD_VAL 0x0008 #define BYTGPIO_PAD_VAL_LEVEL (1 << 0) #define BYTGPIO_PAD_VAL_I_OUTPUT_ENABLED (1 << 1) #define BYTGPIO_PAD_VAL_I_INPUT_ENABLED (1 << 2) #define BYTGPIO_PAD_VAL_DIR_MASK (3 << 1) static inline uint32_t bytgpio_read_4(struct bytgpio_softc *sc, bus_size_t off) { return (bus_read_4(sc->sc_mem_res, off)); } static inline void bytgpio_write_4(struct bytgpio_softc *sc, bus_size_t off, uint32_t val) { bus_write_4(sc->sc_mem_res, off, val); } static device_t bytgpio_get_bus(device_t dev) { struct bytgpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int bytgpio_pin_max(device_t dev, int *maxpin) { struct bytgpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->sc_npins - 1; return (0); } static int bytgpio_valid_pin(struct bytgpio_softc *sc, int pin) { if (pin >= sc->sc_npins || sc->sc_mem_res == NULL) return (EINVAL); return (0); } /* * Returns true if pad configured to be used as GPIO */ static bool bytgpio_pad_is_gpio(struct bytgpio_softc *sc, int pin) { if ((sc->sc_pinpad_map[pin].pad_func == PADCONF_FUNC_ANY) || (sc->sc_pad_funcs[pin] == sc->sc_pinpad_map[pin].pad_func)) return (true); else return (false); } static int bytgpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct bytgpio_softc *sc; sc = device_get_softc(dev); if (bytgpio_valid_pin(sc, pin) != 0) return (EINVAL); *caps = 0; if (bytgpio_pad_is_gpio(sc, pin)) *caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; return (0); } static int bytgpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct bytgpio_softc *sc; uint32_t reg, val; sc = device_get_softc(dev); if (bytgpio_valid_pin(sc, pin) != 0) return (EINVAL); *flags = 0; if (!bytgpio_pad_is_gpio(sc, pin)) return (0); /* Get the current pin state */ BYTGPIO_LOCK(sc); reg = BYGPIO_PIN_REGISTER(sc, pin, BYTGPIO_PAD_VAL); val = bytgpio_read_4(sc, reg); if ((val & BYTGPIO_PAD_VAL_I_OUTPUT_ENABLED) == 0) *flags |= GPIO_PIN_OUTPUT; /* * this bit can be cleared to read current output value * sou output bit takes precedense */ else if ((val & BYTGPIO_PAD_VAL_I_INPUT_ENABLED) == 0) *flags |= GPIO_PIN_INPUT; BYTGPIO_UNLOCK(sc); return (0); } static int bytgpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct bytgpio_softc *sc; uint32_t reg, val; uint32_t allowed; sc = device_get_softc(dev); if (bytgpio_valid_pin(sc, pin) != 0) return (EINVAL); if (bytgpio_pad_is_gpio(sc, pin)) allowed = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; else allowed = 0; /* * Only directtion flag allowed */ if (flags & ~allowed) return (EINVAL); /* * Not both directions simultaneously */ if ((flags & allowed) == allowed) return (EINVAL); /* Set the GPIO mode and state */ BYTGPIO_LOCK(sc); reg = BYGPIO_PIN_REGISTER(sc, pin, BYTGPIO_PAD_VAL); val = bytgpio_read_4(sc, reg); val = val | BYTGPIO_PAD_VAL_DIR_MASK; if (flags & GPIO_PIN_INPUT) val = val & ~BYTGPIO_PAD_VAL_I_INPUT_ENABLED; if (flags & GPIO_PIN_OUTPUT) val = val & ~BYTGPIO_PAD_VAL_I_OUTPUT_ENABLED; bytgpio_write_4(sc, reg, val); BYTGPIO_UNLOCK(sc); return (0); } static int bytgpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct bytgpio_softc *sc; sc = device_get_softc(dev); if (bytgpio_valid_pin(sc, pin) != 0) return (EINVAL); /* Set a very simple name */ snprintf(name, GPIOMAXNAME, "%s%u", sc->sc_bank_prefix, pin); name[GPIOMAXNAME - 1] = '\0'; return (0); } static int bytgpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct bytgpio_softc *sc; uint32_t reg, val; sc = device_get_softc(dev); if (bytgpio_valid_pin(sc, pin) != 0) return (EINVAL); if (!bytgpio_pad_is_gpio(sc, pin)) return (EINVAL); BYTGPIO_LOCK(sc); reg = BYGPIO_PIN_REGISTER(sc, pin, BYTGPIO_PAD_VAL); val = bytgpio_read_4(sc, reg); if (value == GPIO_PIN_LOW) val = val & ~BYTGPIO_PAD_VAL_LEVEL; else val = val | BYTGPIO_PAD_VAL_LEVEL; bytgpio_write_4(sc, reg, val); BYTGPIO_UNLOCK(sc); return (0); } static int bytgpio_pin_get(device_t dev, uint32_t pin, unsigned int *value) { struct bytgpio_softc *sc; uint32_t reg, val; sc = device_get_softc(dev); if (bytgpio_valid_pin(sc, pin) != 0) return (EINVAL); /* * Report non-GPIO pads as pin LOW */ if (!bytgpio_pad_is_gpio(sc, pin)) { *value = GPIO_PIN_LOW; return (0); } BYTGPIO_LOCK(sc); reg = BYGPIO_PIN_REGISTER(sc, pin, BYTGPIO_PAD_VAL); /* * And read actual value */ val = bytgpio_read_4(sc, reg); if (val & BYTGPIO_PAD_VAL_LEVEL) *value = GPIO_PIN_HIGH; else *value = GPIO_PIN_LOW; BYTGPIO_UNLOCK(sc); return (0); } static int bytgpio_pin_toggle(device_t dev, uint32_t pin) { struct bytgpio_softc *sc; uint32_t reg, val; sc = device_get_softc(dev); if (bytgpio_valid_pin(sc, pin) != 0) return (EINVAL); if (!bytgpio_pad_is_gpio(sc, pin)) return (EINVAL); /* Toggle the pin */ BYTGPIO_LOCK(sc); reg = BYGPIO_PIN_REGISTER(sc, pin, BYTGPIO_PAD_VAL); val = bytgpio_read_4(sc, reg); val = val ^ BYTGPIO_PAD_VAL_LEVEL; bytgpio_write_4(sc, reg, val); BYTGPIO_UNLOCK(sc); return (0); } static int bytgpio_probe(device_t dev) { static char *gpio_ids[] = { "INT33FC", NULL }; + int rv; - if (acpi_disabled("gpio") || - ACPI_ID_PROBE(device_get_parent(dev), dev, gpio_ids) == NULL) - return (ENXIO); - - device_set_desc(dev, "Intel Baytrail GPIO Controller"); - return (0); + if (acpi_disabled("gpio")) + return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, gpio_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "Intel Baytrail GPIO Controller"); + return (rv); } static int bytgpio_attach(device_t dev) { struct bytgpio_softc *sc; ACPI_STATUS status; int uid; int pin; uint32_t reg, val; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_handle = acpi_get_handle(dev); status = acpi_GetInteger(sc->sc_handle, "_UID", &uid); if (ACPI_FAILURE(status)) { device_printf(dev, "failed to read _UID\n"); return (ENXIO); } BYTGPIO_LOCK_INIT(sc); switch (uid) { case SCORE_UID: sc->sc_npins = SCORE_PINS; sc->sc_bank_prefix = SCORE_BANK_PREFIX; sc->sc_pinpad_map = bytgpio_score_pins; break; case NCORE_UID: sc->sc_npins = NCORE_PINS; sc->sc_bank_prefix = NCORE_BANK_PREFIX; sc->sc_pinpad_map = bytgpio_ncore_pins; break; case SUS_UID: sc->sc_npins = SUS_PINS; sc->sc_bank_prefix = SUS_BANK_PREFIX; sc->sc_pinpad_map = bytgpio_sus_pins; break; default: device_printf(dev, "invalid _UID value: %d\n", uid); goto error; } sc->sc_pad_funcs = malloc(sizeof(int)*sc->sc_npins, M_DEVBUF, M_WAITOK | M_ZERO); sc->sc_mem_rid = 0; sc->sc_mem_res = bus_alloc_resource_any(sc->sc_dev, SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "can't allocate resource\n"); goto error; } for (pin = 0; pin < sc->sc_npins; pin++) { reg = BYGPIO_PIN_REGISTER(sc, pin, BYTGPIO_PCONF0); val = bytgpio_read_4(sc, reg); sc->sc_pad_funcs[pin] = val & BYTGPIO_PCONF0_FUNC_MASK; } sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) { BYTGPIO_LOCK_DESTROY(sc); bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); return (ENXIO); } return (0); error: BYTGPIO_LOCK_DESTROY(sc); return (ENXIO); } static int bytgpio_detach(device_t dev) { struct bytgpio_softc *sc; sc = device_get_softc(dev); if (sc->sc_busdev) gpiobus_detach_bus(dev); BYTGPIO_LOCK_DESTROY(sc); if (sc->sc_pad_funcs) free(sc->sc_pad_funcs, M_DEVBUF); if (sc->sc_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); return (0); } static device_method_t bytgpio_methods[] = { /* Device interface */ DEVMETHOD(device_probe, bytgpio_probe), DEVMETHOD(device_attach, bytgpio_attach), DEVMETHOD(device_detach, bytgpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, bytgpio_get_bus), DEVMETHOD(gpio_pin_max, bytgpio_pin_max), DEVMETHOD(gpio_pin_getname, bytgpio_pin_getname), DEVMETHOD(gpio_pin_getflags, bytgpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, bytgpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, bytgpio_pin_setflags), DEVMETHOD(gpio_pin_get, bytgpio_pin_get), DEVMETHOD(gpio_pin_set, bytgpio_pin_set), DEVMETHOD(gpio_pin_toggle, bytgpio_pin_toggle), DEVMETHOD_END }; static driver_t bytgpio_driver = { "gpio", bytgpio_methods, sizeof(struct bytgpio_softc), }; static devclass_t bytgpio_devclass; DRIVER_MODULE(bytgpio, acpi, bytgpio_driver, bytgpio_devclass, 0, 0); MODULE_DEPEND(bytgpio, acpi, 1, 1, 1); MODULE_DEPEND(bytgpio, gpiobus, 1, 1, 1); Index: head/sys/dev/gpio/chvgpio.c =================================================================== --- head/sys/dev/gpio/chvgpio.c (revision 339753) +++ head/sys/dev/gpio/chvgpio.c (revision 339754) @@ -1,523 +1,525 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2017 Tom Jones * 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. * */ /* * Copyright (c) 2016 Mark Kettenis * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "opt_platform.h" #include "opt_acpi.h" #include "gpio_if.h" #include "chvgpio_reg.h" /* * Macros for driver mutex locking */ #define CHVGPIO_LOCK(_sc) mtx_lock_spin(&(_sc)->sc_mtx) #define CHVGPIO_UNLOCK(_sc) mtx_unlock_spin(&(_sc)->sc_mtx) #define CHVGPIO_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ "chvgpio", MTX_SPIN) #define CHVGPIO_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) #define CHVGPIO_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define CHVGPIO_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED) struct chvgpio_softc { device_t sc_dev; device_t sc_busdev; struct mtx sc_mtx; ACPI_HANDLE sc_handle; int sc_mem_rid; struct resource *sc_mem_res; int sc_irq_rid; struct resource *sc_irq_res; void *intr_handle; const char *sc_bank_prefix; const int *sc_pins; int sc_npins; int sc_ngroups; const char **sc_pin_names; }; static void chvgpio_intr(void *); static int chvgpio_probe(device_t); static int chvgpio_attach(device_t); static int chvgpio_detach(device_t); static inline int chvgpio_pad_cfg0_offset(int pin) { return (CHVGPIO_PAD_CFG0 + 1024 * (pin / 15) + 8 * (pin % 15)); } static inline int chvgpio_read_pad_cfg0(struct chvgpio_softc *sc, int pin) { return bus_read_4(sc->sc_mem_res, chvgpio_pad_cfg0_offset(pin)); } static inline void chvgpio_write_pad_cfg0(struct chvgpio_softc *sc, int pin, uint32_t val) { bus_write_4(sc->sc_mem_res, chvgpio_pad_cfg0_offset(pin), val); } static inline int chvgpio_read_pad_cfg1(struct chvgpio_softc *sc, int pin) { return bus_read_4(sc->sc_mem_res, chvgpio_pad_cfg0_offset(pin) + 4); } static device_t chvgpio_get_bus(device_t dev) { struct chvgpio_softc *sc; sc = device_get_softc(dev); return (sc->sc_busdev); } static int chvgpio_pin_max(device_t dev, int *maxpin) { struct chvgpio_softc *sc; sc = device_get_softc(dev); *maxpin = sc->sc_npins - 1; return (0); } static int chvgpio_valid_pin(struct chvgpio_softc *sc, int pin) { if (pin < 0) return EINVAL; if ((pin / 15) >= sc->sc_ngroups) return EINVAL; if ((pin % 15) >= sc->sc_pins[pin / 15]) return EINVAL; return (0); } static int chvgpio_pin_getname(device_t dev, uint32_t pin, char *name) { struct chvgpio_softc *sc; sc = device_get_softc(dev); if (chvgpio_valid_pin(sc, pin) != 0) return (EINVAL); /* return pin name from datasheet */ snprintf(name, GPIOMAXNAME, "%s", sc->sc_pin_names[pin]); name[GPIOMAXNAME - 1] = '\0'; return (0); } static int chvgpio_pin_getcaps(device_t dev, uint32_t pin, uint32_t *caps) { struct chvgpio_softc *sc; sc = device_get_softc(dev); if (chvgpio_valid_pin(sc, pin) != 0) return (EINVAL); *caps = 0; if (chvgpio_valid_pin(sc, pin)) *caps = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; return (0); } static int chvgpio_pin_getflags(device_t dev, uint32_t pin, uint32_t *flags) { struct chvgpio_softc *sc; uint32_t val; sc = device_get_softc(dev); if (chvgpio_valid_pin(sc, pin) != 0) return (EINVAL); *flags = 0; /* Get the current pin state */ CHVGPIO_LOCK(sc); val = chvgpio_read_pad_cfg0(sc, pin); if (val & CHVGPIO_PAD_CFG0_GPIOCFG_GPIO || val & CHVGPIO_PAD_CFG0_GPIOCFG_GPO) *flags |= GPIO_PIN_OUTPUT; if (val & CHVGPIO_PAD_CFG0_GPIOCFG_GPIO || val & CHVGPIO_PAD_CFG0_GPIOCFG_GPI) *flags |= GPIO_PIN_INPUT; val = chvgpio_read_pad_cfg1(sc, pin); CHVGPIO_UNLOCK(sc); return (0); } static int chvgpio_pin_setflags(device_t dev, uint32_t pin, uint32_t flags) { struct chvgpio_softc *sc; uint32_t val; uint32_t allowed; sc = device_get_softc(dev); if (chvgpio_valid_pin(sc, pin) != 0) return (EINVAL); allowed = GPIO_PIN_INPUT | GPIO_PIN_OUTPUT; /* * Only direction flag allowed */ if (flags & ~allowed) return (EINVAL); /* * Not both directions simultaneously */ if ((flags & allowed) == allowed) return (EINVAL); /* Set the GPIO mode and state */ CHVGPIO_LOCK(sc); val = chvgpio_read_pad_cfg0(sc, pin); if (flags & GPIO_PIN_INPUT) val = val & CHVGPIO_PAD_CFG0_GPIOCFG_GPI; if (flags & GPIO_PIN_OUTPUT) val = val & CHVGPIO_PAD_CFG0_GPIOCFG_GPO; chvgpio_write_pad_cfg0(sc, pin, val); CHVGPIO_UNLOCK(sc); return (0); } static int chvgpio_pin_set(device_t dev, uint32_t pin, unsigned int value) { struct chvgpio_softc *sc; uint32_t val; sc = device_get_softc(dev); if (chvgpio_valid_pin(sc, pin) != 0) return (EINVAL); CHVGPIO_LOCK(sc); val = chvgpio_read_pad_cfg0(sc, pin); if (value == GPIO_PIN_LOW) val = val & ~CHVGPIO_PAD_CFG0_GPIOTXSTATE; else val = val | CHVGPIO_PAD_CFG0_GPIOTXSTATE; chvgpio_write_pad_cfg0(sc, pin, val); CHVGPIO_UNLOCK(sc); return (0); } static int chvgpio_pin_get(device_t dev, uint32_t pin, unsigned int *value) { struct chvgpio_softc *sc; uint32_t val; sc = device_get_softc(dev); if (chvgpio_valid_pin(sc, pin) != 0) return (EINVAL); CHVGPIO_LOCK(sc); /* Read pin value */ val = chvgpio_read_pad_cfg0(sc, pin); if (val & CHVGPIO_PAD_CFG0_GPIORXSTATE) *value = GPIO_PIN_HIGH; else *value = GPIO_PIN_LOW; CHVGPIO_UNLOCK(sc); return (0); } static int chvgpio_pin_toggle(device_t dev, uint32_t pin) { struct chvgpio_softc *sc; uint32_t val; sc = device_get_softc(dev); if (chvgpio_valid_pin(sc, pin) != 0) return (EINVAL); CHVGPIO_LOCK(sc); /* Toggle the pin */ val = chvgpio_read_pad_cfg0(sc, pin); val = val ^ CHVGPIO_PAD_CFG0_GPIOTXSTATE; chvgpio_write_pad_cfg0(sc, pin, val); CHVGPIO_UNLOCK(sc); return (0); } static char *chvgpio_hids[] = { "INT33FF", NULL }; static int chvgpio_probe(device_t dev) { - if (acpi_disabled("chvgpio") || - ACPI_ID_PROBE(device_get_parent(dev), dev, chvgpio_hids) == NULL) + int rv; + + if (acpi_disabled("chvgpio")) return (ENXIO); - - device_set_desc(dev, "Intel Cherry View GPIO"); - return (0); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, chvgpio_hids, NULL); + if (rv <= 0) + device_set_desc(dev, "Intel Cherry View GPIO"); + return (rv); } static int chvgpio_attach(device_t dev) { struct chvgpio_softc *sc; ACPI_STATUS status; int uid; int i; int error; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_handle = acpi_get_handle(dev); status = acpi_GetInteger(sc->sc_handle, "_UID", &uid); if (ACPI_FAILURE(status)) { device_printf(dev, "failed to read _UID\n"); return (ENXIO); } CHVGPIO_LOCK_INIT(sc); switch (uid) { case SW_UID: sc->sc_bank_prefix = SW_BANK_PREFIX; sc->sc_pins = chv_southwest_pins; sc->sc_pin_names = chv_southwest_pin_names; break; case N_UID: sc->sc_bank_prefix = N_BANK_PREFIX; sc->sc_pins = chv_north_pins; sc->sc_pin_names = chv_north_pin_names; break; case E_UID: sc->sc_bank_prefix = E_BANK_PREFIX; sc->sc_pins = chv_east_pins; sc->sc_pin_names = chv_east_pin_names; break; case SE_UID: sc->sc_bank_prefix = SE_BANK_PREFIX; sc->sc_pins = chv_southeast_pins; sc->sc_pin_names = chv_southeast_pin_names; break; default: device_printf(dev, "invalid _UID value: %d\n", uid); return (ENXIO); } for (i = 0; sc->sc_pins[i] >= 0; i++) { sc->sc_npins += sc->sc_pins[i]; sc->sc_ngroups++; } sc->sc_mem_rid = 0; sc->sc_mem_res = bus_alloc_resource_any(sc->sc_dev, SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { CHVGPIO_LOCK_DESTROY(sc); device_printf(dev, "can't allocate memory resource\n"); return (ENOMEM); } sc->sc_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE); if (!sc->sc_irq_res) { CHVGPIO_LOCK_DESTROY(sc); bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); device_printf(dev, "can't allocate irq resource\n"); return (ENOMEM); } error = bus_setup_intr(sc->sc_dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, chvgpio_intr, sc, &sc->intr_handle); if (error) { device_printf(sc->sc_dev, "unable to setup irq: error %d\n", error); CHVGPIO_LOCK_DESTROY(sc); bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); return (ENXIO); } /* Mask and ack all interrupts. */ bus_write_4(sc->sc_mem_res, CHVGPIO_INTERRUPT_MASK, 0); bus_write_4(sc->sc_mem_res, CHVGPIO_INTERRUPT_STATUS, 0xffff); sc->sc_busdev = gpiobus_attach_bus(dev); if (sc->sc_busdev == NULL) { CHVGPIO_LOCK_DESTROY(sc); bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); return (ENXIO); } return (0); } static void chvgpio_intr(void *arg) { struct chvgpio_softc *sc = arg; uint32_t reg; int line; reg = bus_read_4(sc->sc_mem_res, CHVGPIO_INTERRUPT_STATUS); for (line = 0; line < 16; line++) { if ((reg & (1 << line)) == 0) continue; bus_write_4(sc->sc_mem_res, CHVGPIO_INTERRUPT_STATUS, 1 << line); } } static int chvgpio_detach(device_t dev) { struct chvgpio_softc *sc; sc = device_get_softc(dev); if (sc->sc_busdev) gpiobus_detach_bus(dev); if (sc->intr_handle != NULL) bus_teardown_intr(sc->sc_dev, sc->sc_irq_res, sc->intr_handle); if (sc->sc_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); if (sc->sc_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); CHVGPIO_LOCK_DESTROY(sc); return (0); } static device_method_t chvgpio_methods[] = { DEVMETHOD(device_probe, chvgpio_probe), DEVMETHOD(device_attach, chvgpio_attach), DEVMETHOD(device_detach, chvgpio_detach), /* GPIO protocol */ DEVMETHOD(gpio_get_bus, chvgpio_get_bus), DEVMETHOD(gpio_pin_max, chvgpio_pin_max), DEVMETHOD(gpio_pin_getname, chvgpio_pin_getname), DEVMETHOD(gpio_pin_getflags, chvgpio_pin_getflags), DEVMETHOD(gpio_pin_getcaps, chvgpio_pin_getcaps), DEVMETHOD(gpio_pin_setflags, chvgpio_pin_setflags), DEVMETHOD(gpio_pin_get, chvgpio_pin_get), DEVMETHOD(gpio_pin_set, chvgpio_pin_set), DEVMETHOD(gpio_pin_toggle, chvgpio_pin_toggle), DEVMETHOD_END }; static driver_t chvgpio_driver = { .name = "gpio", .methods = chvgpio_methods, .size = sizeof(struct chvgpio_softc) }; static devclass_t chvgpio_devclass; DRIVER_MODULE(chvgpio, acpi, chvgpio_driver, chvgpio_devclass, NULL , NULL); MODULE_DEPEND(chvgpio, acpi, 1, 1, 1); MODULE_DEPEND(chvgpio, gpiobus, 1, 1, 1); MODULE_VERSION(chvgpio, 1); Index: head/sys/dev/hyperv/vmbus/vmbus_res.c =================================================================== --- head/sys/dev/hyperv/vmbus/vmbus_res.c (revision 339753) +++ head/sys/dev/hyperv/vmbus/vmbus_res.c (revision 339754) @@ -1,98 +1,99 @@ /*- * Copyright (c) 2017 Microsoft Corp. * 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 unmodified, 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 ``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 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include "acpi_if.h" #include "bus_if.h" static int vmbus_res_probe(device_t); static int vmbus_res_attach(device_t); static int vmbus_res_detach(device_t); static device_method_t vmbus_res_methods[] = { /* Device interface */ DEVMETHOD(device_probe, vmbus_res_probe), DEVMETHOD(device_attach, vmbus_res_attach), DEVMETHOD(device_detach, vmbus_res_detach), DEVMETHOD(device_shutdown, bus_generic_shutdown), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), DEVMETHOD_END }; static driver_t vmbus_res_driver = { "vmbus_res", vmbus_res_methods, 1 }; static devclass_t vmbus_res_devclass; DRIVER_MODULE(vmbus_res, acpi, vmbus_res_driver, vmbus_res_devclass, NULL, NULL); MODULE_DEPEND(vmbus_res, acpi, 1, 1, 1); MODULE_VERSION(vmbus_res, 1); static int vmbus_res_probe(device_t dev) { char *id[] = { "VMBUS", NULL }; - - if (ACPI_ID_PROBE(device_get_parent(dev), dev, id) == NULL || - device_get_unit(dev) != 0 || vm_guest != VM_GUEST_HV || + int rv; + + if (device_get_unit(dev) != 0 || vm_guest != VM_GUEST_HV || (hyperv_features & CPUID_HV_MSR_SYNIC) == 0) return (ENXIO); - - device_set_desc(dev, "Hyper-V Vmbus Resource"); - return (BUS_PROBE_DEFAULT); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, id, NULL); + if (rv <= 0) + device_set_desc(dev, "Hyper-V Vmbus Resource"); + return (rv); } static int vmbus_res_attach(device_t dev __unused) { return (0); } static int vmbus_res_detach(device_t dev __unused) { return (0); } Index: head/sys/dev/ichiic/ig4_acpi.c =================================================================== --- head/sys/dev/ichiic/ig4_acpi.c (revision 339753) +++ head/sys/dev/ichiic/ig4_acpi.c (revision 339754) @@ -1,179 +1,181 @@ /*- * Copyright (c) 2016 Oleksandr Tymoshenko * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int ig4iic_acpi_probe(device_t dev); static int ig4iic_acpi_attach(device_t dev); static int ig4iic_acpi_detach(device_t dev); static char *ig4iic_ids[] = { "INT33C2", "INT33C3", "INT3432", "INT3433", "80860F41", "808622C1", "AMDI0510", "AMDI0010", "APMC0D0F", NULL }; static int ig4iic_acpi_probe(device_t dev) { ig4iic_softc_t *sc; char *hid; - + int rv; sc = device_get_softc(dev); if (acpi_disabled("ig4iic")) return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, ig4iic_ids, &hid); + if (rv > 0){ + return (rv); + } - hid = ACPI_ID_PROBE(device_get_parent(dev), dev, ig4iic_ids); - if (hid == NULL) - return (ENXIO); + if (strcmp("AMDI0010", hid) == 0) + sc->access_intr_mask = 1; - if (strcmp("AMDI0010", hid) == 0) - sc->access_intr_mask = 1; - - device_set_desc(dev, "Designware I2C Controller"); - return (0); + + if (rv <= 0) + device_set_desc(dev, "Designware I2C Controller"); + return (rv); } static int ig4iic_acpi_attach(device_t dev) { ig4iic_softc_t *sc; int error; sc = device_get_softc(dev); sc->dev = dev; /* All the HIDs matched are Atom SOCs. */ sc->version = IG4_ATOM; sc->regs_rid = 0; sc->regs_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &sc->regs_rid, RF_ACTIVE); if (sc->regs_res == NULL) { device_printf(dev, "unable to map registers\n"); ig4iic_acpi_detach(dev); return (ENXIO); } sc->intr_rid = 0; sc->intr_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->intr_rid, RF_SHAREABLE | RF_ACTIVE); if (sc->intr_res == NULL) { device_printf(dev, "unable to map interrupt\n"); ig4iic_acpi_detach(dev); return (ENXIO); } sc->platform_attached = 1; error = ig4iic_attach(sc); if (error) ig4iic_acpi_detach(dev); return (error); } static int ig4iic_acpi_detach(device_t dev) { ig4iic_softc_t *sc = device_get_softc(dev); int error; if (sc->platform_attached) { error = ig4iic_detach(sc); if (error) return (error); sc->platform_attached = 0; } if (sc->intr_res) { bus_release_resource(dev, SYS_RES_IRQ, sc->intr_rid, sc->intr_res); sc->intr_res = NULL; } if (sc->regs_res) { bus_release_resource(dev, SYS_RES_MEMORY, sc->regs_rid, sc->regs_res); sc->regs_res = NULL; } return (0); } static device_method_t ig4iic_acpi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ig4iic_acpi_probe), DEVMETHOD(device_attach, ig4iic_acpi_attach), DEVMETHOD(device_detach, ig4iic_acpi_detach), /* iicbus interface */ DEVMETHOD(iicbus_transfer, ig4iic_transfer), DEVMETHOD(iicbus_reset, ig4iic_reset), DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD_END }; static driver_t ig4iic_acpi_driver = { "ig4iic_acpi", ig4iic_acpi_methods, sizeof(struct ig4iic_softc), }; static devclass_t ig4iic_acpi_devclass; DRIVER_MODULE(ig4iic_acpi, acpi, ig4iic_acpi_driver, ig4iic_acpi_devclass, 0, 0); MODULE_DEPEND(ig4iic_acpi, acpi, 1, 1, 1); MODULE_DEPEND(ig4iic_acpi, pci, 1, 1, 1); MODULE_DEPEND(ig4iic_acpi, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_VERSION(ig4iic_acpi, 1); Index: head/sys/dev/intel/spi.c =================================================================== --- head/sys/dev/intel/spi.c (revision 339753) +++ head/sys/dev/intel/spi.c (revision 339754) @@ -1,533 +1,534 @@ /*- * Copyright (c) 2016 Oleksandr Tymoshenko * 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. */ #include __FBSDID("$FreeBSD$"); #include "opt_acpi.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "spibus_if.h" /** * Macros for driver mutex locking */ #define INTELSPI_LOCK(_sc) mtx_lock(&(_sc)->sc_mtx) #define INTELSPI_UNLOCK(_sc) mtx_unlock(&(_sc)->sc_mtx) #define INTELSPI_LOCK_INIT(_sc) \ mtx_init(&_sc->sc_mtx, device_get_nameunit((_sc)->sc_dev), \ "intelspi", MTX_DEF) #define INTELSPI_LOCK_DESTROY(_sc) mtx_destroy(&(_sc)->sc_mtx) #define INTELSPI_ASSERT_LOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_OWNED) #define INTELSPI_ASSERT_UNLOCKED(_sc) mtx_assert(&(_sc)->sc_mtx, MA_NOTOWNED) #define INTELSPI_WRITE(_sc, _off, _val) \ bus_write_4((_sc)->sc_mem_res, (_off), (_val)) #define INTELSPI_READ(_sc, _off) \ bus_read_4((_sc)->sc_mem_res, (_off)) #define INTELSPI_BUSY 0x1 #define TX_FIFO_THRESHOLD 2 #define RX_FIFO_THRESHOLD 2 #define CLOCK_DIV_10MHZ 5 #define DATA_SIZE_8BITS 8 #define CS_LOW 0 #define CS_HIGH 1 #define INTELSPI_SSPREG_SSCR0 0x0 #define SSCR0_SCR(n) (((n) - 1) << 8) #define SSCR0_SSE (1 << 7) #define SSCR0_FRF_SPI (0 << 4) #define SSCR0_DSS(n) (((n) - 1) << 0) #define INTELSPI_SSPREG_SSCR1 0x4 #define SSCR1_TINTE (1 << 19) #define SSCR1_RFT(n) (((n) - 1) << 10) #define SSCR1_RFT_MASK (0xf << 10) #define SSCR1_TFT(n) (((n) - 1) << 6) #define SSCR1_SPI_SPH (1 << 4) #define SSCR1_SPI_SPO (1 << 3) #define SSCR1_MODE_MASK (SSCR1_SPI_SPO | SSCR1_SPI_SPH) #define SSCR1_MODE_0 (0) #define SSCR1_MODE_1 (SSCR1_SPI_SPH) #define SSCR1_MODE_2 (SSCR1_SPI_SPO) #define SSCR1_MODE_3 (SSCR1_SPI_SPO | SSCR1_SPI_SPH) #define SSCR1_TIE (1 << 1) #define SSCR1_RIE (1 << 0) #define INTELSPI_SSPREG_SSSR 0x8 #define SSSR_RFL_MASK (0xf << 12) #define SSSR_TFL_MASK (0xf << 8) #define SSSR_RNE (1 << 3) #define SSSR_TNF (1 << 2) #define INTELSPI_SSPREG_SSITR 0xC #define INTELSPI_SSPREG_SSDR 0x10 #define INTELSPI_SSPREG_SSTO 0x28 #define INTELSPI_SSPREG_SSPSP 0x2C #define INTELSPI_SSPREG_SSTSA 0x30 #define INTELSPI_SSPREG_SSRSA 0x34 #define INTELSPI_SSPREG_SSTSS 0x38 #define INTELSPI_SSPREG_SSACD 0x3C #define INTELSPI_SSPREG_ITF 0x40 #define INTELSPI_SSPREG_SITF 0x44 #define INTELSPI_SSPREG_SIRF 0x48 #define INTELSPI_SSPREG_PRV_CLOCK_PARAMS 0x400 #define INTELSPI_SSPREG_RESETS 0x404 #define INTELSPI_SSPREG_GENERAL 0x408 #define INTELSPI_SSPREG_SSP_REG 0x40C #define INTELSPI_SSPREG_SPI_CS_CTRL 0x418 #define SPI_CS_CTRL_CS_MASK (3) #define SPI_CS_CTRL_SW_MODE (1 << 0) #define SPI_CS_CTRL_HW_MODE (1 << 0) #define SPI_CS_CTRL_CS_HIGH (1 << 1) #define SPI_CS_CTRL_CS_LOW (0 << 1) struct intelspi_softc { ACPI_HANDLE sc_handle; device_t sc_dev; struct mtx sc_mtx; int sc_mem_rid; struct resource *sc_mem_res; int sc_irq_rid; struct resource *sc_irq_res; void *sc_irq_ih; struct spi_command *sc_cmd; uint32_t sc_len; uint32_t sc_read; uint32_t sc_flags; uint32_t sc_written; }; static int intelspi_probe(device_t dev); static int intelspi_attach(device_t dev); static int intelspi_detach(device_t dev); static void intelspi_intr(void *); static int intelspi_txfifo_full(struct intelspi_softc *sc) { uint32_t sssr; INTELSPI_ASSERT_LOCKED(sc); sssr = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); if (sssr & SSSR_TNF) return (0); return (1); } static int intelspi_rxfifo_empty(struct intelspi_softc *sc) { uint32_t sssr; INTELSPI_ASSERT_LOCKED(sc); sssr = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); if (sssr & SSSR_RNE) return (0); else return (1); } static void intelspi_fill_tx_fifo(struct intelspi_softc *sc) { struct spi_command *cmd; uint32_t written; uint8_t *data; INTELSPI_ASSERT_LOCKED(sc); cmd = sc->sc_cmd; while (sc->sc_written < sc->sc_len && !intelspi_txfifo_full(sc)) { data = (uint8_t *)cmd->tx_cmd; written = sc->sc_written++; if (written >= cmd->tx_cmd_sz) { data = (uint8_t *)cmd->tx_data; written -= cmd->tx_cmd_sz; } INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSDR, data[written]); } } static void intelspi_drain_rx_fifo(struct intelspi_softc *sc) { struct spi_command *cmd; uint32_t read; uint8_t *data; INTELSPI_ASSERT_LOCKED(sc); cmd = sc->sc_cmd; while (sc->sc_read < sc->sc_len && !intelspi_rxfifo_empty(sc)) { data = (uint8_t *)cmd->rx_cmd; read = sc->sc_read++; if (read >= cmd->rx_cmd_sz) { data = (uint8_t *)cmd->rx_data; read -= cmd->rx_cmd_sz; } data[read] = INTELSPI_READ(sc, INTELSPI_SSPREG_SSDR) & 0xff; } } static int intelspi_transaction_done(struct intelspi_softc *sc) { int txfifo_empty; uint32_t sssr; INTELSPI_ASSERT_LOCKED(sc); if (sc->sc_written != sc->sc_len || sc->sc_read != sc->sc_len) return (0); sssr = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); txfifo_empty = ((sssr & SSSR_TFL_MASK) == 0) && (sssr & SSSR_TNF); if (txfifo_empty && !(sssr & SSSR_RNE)) return (1); return (0); } static int intelspi_transact(struct intelspi_softc *sc) { INTELSPI_ASSERT_LOCKED(sc); /* TX - Fill up the FIFO. */ intelspi_fill_tx_fifo(sc); /* RX - Drain the FIFO. */ intelspi_drain_rx_fifo(sc); /* Check for end of transfer. */ return intelspi_transaction_done(sc); } static void intelspi_intr(void *arg) { struct intelspi_softc *sc; uint32_t reg; sc = (struct intelspi_softc *)arg; INTELSPI_LOCK(sc); if ((sc->sc_flags & INTELSPI_BUSY) == 0) { INTELSPI_UNLOCK(sc); return; } /* Check if SSP if off */ reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SSSR); if (reg == 0xffffffffU) { INTELSPI_UNLOCK(sc); return; } /* Check for end of transfer. */ if (intelspi_transact(sc)) { /* Disable interrupts */ reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); reg &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, reg); wakeup(sc->sc_dev); } INTELSPI_UNLOCK(sc); } static void intelspi_init(struct intelspi_softc *sc) { uint32_t reg; INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, 0); /* Manual CS control */ reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SPI_CS_CTRL); reg &= ~(SPI_CS_CTRL_CS_MASK); reg |= (SPI_CS_CTRL_SW_MODE | SPI_CS_CTRL_CS_HIGH); INTELSPI_WRITE(sc, INTELSPI_SSPREG_SPI_CS_CTRL, reg); /* Set TX/RX FIFO IRQ threshold levels */ reg = SSCR1_TFT(TX_FIFO_THRESHOLD) | SSCR1_RFT(RX_FIFO_THRESHOLD); /* * Set SPI mode. This should be part of transaction or sysctl */ reg |= SSCR1_MODE_0; INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, reg); /* * Parent clock on Minowboard Turbot is 50MHz * divide it by 5 to set to more or less reasonable * value. But this should be part of transaction config * or sysctl */ reg = SSCR0_SCR(CLOCK_DIV_10MHZ); /* Put SSP in SPI mode */ reg |= SSCR0_FRF_SPI; /* Data size */ reg |= SSCR0_DSS(DATA_SIZE_8BITS); /* Enable SSP */ reg |= SSCR0_SSE; INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR0, reg); } static void intelspi_set_cs(struct intelspi_softc *sc, int level) { uint32_t reg; reg = INTELSPI_READ(sc, INTELSPI_SSPREG_SPI_CS_CTRL); reg &= ~(SPI_CS_CTRL_CS_MASK); reg |= SPI_CS_CTRL_SW_MODE; if (level == CS_HIGH) reg |= SPI_CS_CTRL_CS_HIGH; else reg |= SPI_CS_CTRL_CS_LOW; INTELSPI_WRITE(sc, INTELSPI_SSPREG_SPI_CS_CTRL, reg); } static int intelspi_transfer(device_t dev, device_t child, struct spi_command *cmd) { struct intelspi_softc *sc; int err; uint32_t sscr1; sc = device_get_softc(dev); err = 0; KASSERT(cmd->tx_cmd_sz == cmd->rx_cmd_sz, ("TX/RX command sizes should be equal")); KASSERT(cmd->tx_data_sz == cmd->rx_data_sz, ("TX/RX data sizes should be equal")); INTELSPI_LOCK(sc); /* If the controller is in use wait until it is available. */ while (sc->sc_flags & INTELSPI_BUSY) { err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", 0); if (err == EINTR) { INTELSPI_UNLOCK(sc); return (err); } } /* Now we have control over SPI controller. */ sc->sc_flags = INTELSPI_BUSY; /* Save a pointer to the SPI command. */ sc->sc_cmd = cmd; sc->sc_read = 0; sc->sc_written = 0; sc->sc_len = cmd->tx_cmd_sz + cmd->tx_data_sz; /* Enable CS */ intelspi_set_cs(sc, CS_LOW); /* Transfer as much as possible to FIFOs */ if (!intelspi_transact(sc)) { /* If FIFO is not large enough - enable interrupts */ sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); sscr1 |= (SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1); /* and wait for transaction to complete */ err = mtx_sleep(dev, &sc->sc_mtx, 0, "intelspi", hz * 2); } /* de-asser CS */ intelspi_set_cs(sc, CS_HIGH); /* Clear transaction details */ sc->sc_cmd = NULL; sc->sc_read = 0; sc->sc_written = 0; sc->sc_len = 0; /* Make sure the SPI engine and interrupts are disabled. */ sscr1 = INTELSPI_READ(sc, INTELSPI_SSPREG_SSCR1); sscr1 &= ~(SSCR1_TIE | SSCR1_RIE | SSCR1_TINTE); INTELSPI_WRITE(sc, INTELSPI_SSPREG_SSCR1, sscr1); /* Release the controller and wakeup the next thread waiting for it. */ sc->sc_flags = 0; wakeup_one(dev); INTELSPI_UNLOCK(sc); /* * Check for transfer timeout. The SPI controller doesn't * return errors. */ if (err == EWOULDBLOCK) { device_printf(sc->sc_dev, "transfer timeout\n"); err = EIO; } return (err); } static int intelspi_probe(device_t dev) { static char *gpio_ids[] = { "80860F0E", NULL }; - - if (acpi_disabled("spi") || - ACPI_ID_PROBE(device_get_parent(dev), dev, gpio_ids) == NULL) - return (ENXIO); - - device_set_desc(dev, "Intel SPI Controller"); - return (0); + int rv; + + if (acpi_disabled("spi") ) + return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, gpio_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "Intel SPI Controller"); + return (rv); } static int intelspi_attach(device_t dev) { struct intelspi_softc *sc; sc = device_get_softc(dev); sc->sc_dev = dev; sc->sc_handle = acpi_get_handle(dev); INTELSPI_LOCK_INIT(sc); sc->sc_mem_rid = 0; sc->sc_mem_res = bus_alloc_resource_any(sc->sc_dev, SYS_RES_MEMORY, &sc->sc_mem_rid, RF_ACTIVE); if (sc->sc_mem_res == NULL) { device_printf(dev, "can't allocate memory resource\n"); goto error; } sc->sc_irq_rid = 0; sc->sc_irq_res = bus_alloc_resource_any(sc->sc_dev, SYS_RES_IRQ, &sc->sc_irq_rid, RF_ACTIVE); if (sc->sc_irq_res == NULL) { device_printf(dev, "can't allocate IRQ resource\n"); goto error; } /* Hook up our interrupt handler. */ if (bus_setup_intr(dev, sc->sc_irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, intelspi_intr, sc, &sc->sc_irq_ih)) { device_printf(dev, "cannot setup the interrupt handler\n"); goto error; } intelspi_init(sc); device_add_child(dev, "spibus", -1); return (bus_generic_attach(dev)); error: INTELSPI_LOCK_DESTROY(sc); if (sc->sc_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); if (sc->sc_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); return (ENXIO); } static int intelspi_detach(device_t dev) { struct intelspi_softc *sc; sc = device_get_softc(dev); INTELSPI_LOCK_DESTROY(sc); if (sc->sc_irq_ih) bus_teardown_intr(dev, sc->sc_irq_res, sc->sc_irq_ih); if (sc->sc_mem_res != NULL) bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_mem_rid, sc->sc_mem_res); if (sc->sc_irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->sc_irq_rid, sc->sc_irq_res); return (bus_generic_detach(dev)); } static device_method_t intelspi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, intelspi_probe), DEVMETHOD(device_attach, intelspi_attach), DEVMETHOD(device_detach, intelspi_detach), /* SPI interface */ DEVMETHOD(spibus_transfer, intelspi_transfer), DEVMETHOD_END }; static driver_t intelspi_driver = { "spi", intelspi_methods, sizeof(struct intelspi_softc), }; static devclass_t intelspi_devclass; DRIVER_MODULE(intelspi, acpi, intelspi_driver, intelspi_devclass, 0, 0); MODULE_DEPEND(intelspi, acpi, 1, 1, 1); MODULE_DEPEND(intelspi, spibus, 1, 1, 1); Index: head/sys/dev/ipmi/ipmi_acpi.c =================================================================== --- head/sys/dev/ipmi/ipmi_acpi.c (revision 339753) +++ head/sys/dev/ipmi/ipmi_acpi.c (revision 339754) @@ -1,213 +1,214 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2006 IronPort Systems Inc. * 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include /* Hooks for the ACPI CA debugging infrastructure */ #define _COMPONENT ACPI_BUTTON ACPI_MODULE_NAME("IPMI") #ifdef LOCAL_MODULE #include #include #else #include #include #endif static int ipmi_acpi_probe(device_t); static int ipmi_acpi_attach(device_t); int ipmi_acpi_probe(device_t dev) { static char *ipmi_ids[] = {"IPI0001", NULL}; + int rv; if (ipmi_attached) return (EBUSY); - if (acpi_disabled("ipmi") || - ACPI_ID_PROBE(device_get_parent(dev), dev, ipmi_ids) == NULL) + if (acpi_disabled("ipmi")) return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, ipmi_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "IPMI System Interface"); - device_set_desc(dev, "IPMI System Interface"); - - return (0); + return (rv); } static int ipmi_acpi_attach(device_t dev) { ACPI_HANDLE devh; const char *mode; struct ipmi_get_info info; struct ipmi_softc *sc = device_get_softc(dev); int count, error, flags, i, type; int interface_type = 0, interface_version = 0; error = 0; devh = acpi_get_handle(dev); if (ACPI_FAILURE(acpi_GetInteger(devh, "_IFT", &interface_type))) return (ENXIO); if (ACPI_FAILURE(acpi_GetInteger(devh, "_SRV", &interface_version))) return (ENXIO); switch (interface_type) { case KCS_MODE: count = 2; mode = "KCS"; break; case SMIC_MODE: count = 3; mode = "SMIC"; break; case BT_MODE: device_printf(dev, "BT interface not supported\n"); return (ENXIO); case SSIF_MODE: if (ACPI_FAILURE(acpi_GetInteger(devh, "_ADR", &flags))) return (ENXIO); info.address = flags; device_printf(dev, "SSIF interface not supported on ACPI\n"); return (0); default: return (ENXIO); } if (bus_get_resource(dev, SYS_RES_IOPORT, 0, NULL, NULL) == 0) type = SYS_RES_IOPORT; else if (bus_get_resource(dev, SYS_RES_MEMORY, 0, NULL, NULL) == 0) type = SYS_RES_MEMORY; else { device_printf(dev, "unknown resource type\n"); return (ENXIO); } sc->ipmi_io_rid = 0; sc->ipmi_io_res[0] = bus_alloc_resource_any(dev, type, &sc->ipmi_io_rid, RF_ACTIVE); sc->ipmi_io_type = type; sc->ipmi_io_spacing = 1; if (sc->ipmi_io_res[0] == NULL) { device_printf(dev, "couldn't configure I/O resource\n"); return (ENXIO); } /* If we have multiple resources, allocate up to MAX_RES. */ for (i = 1; i < MAX_RES; i++) { sc->ipmi_io_rid = i; sc->ipmi_io_res[i] = bus_alloc_resource_any(dev, type, &sc->ipmi_io_rid, RF_ACTIVE); if (sc->ipmi_io_res[i] == NULL) break; } sc->ipmi_io_rid = 0; /* If we have multiple resources, make sure we have enough of them. */ if (sc->ipmi_io_res[1] != NULL && sc->ipmi_io_res[count - 1] == NULL) { device_printf(dev, "too few I/O resources\n"); error = ENXIO; goto bad; } device_printf(dev, "%s mode found at %s 0x%jx on %s\n", mode, type == SYS_RES_IOPORT ? "io" : "mem", (uintmax_t)rman_get_start(sc->ipmi_io_res[0]), device_get_name(device_get_parent(dev))); sc->ipmi_dev = dev; /* * Setup an interrupt if we have an interrupt resource. We * don't support GPE interrupts via _GPE yet. */ sc->ipmi_irq_rid = 0; sc->ipmi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE); /* Warn if _GPE exists. */ if (ACPI_SUCCESS(AcpiEvaluateObject(devh, "_GPE", NULL, NULL))) device_printf(dev, "_GPE support not implemented\n"); /* * We assume an alignment of 1 byte as currently the IPMI spec * doesn't provide any way to determine the alignment via ACPI. */ switch (interface_type) { case KCS_MODE: error = ipmi_kcs_attach(sc); if (error) goto bad; break; case SMIC_MODE: error = ipmi_smic_attach(sc); if (error) goto bad; break; } error = ipmi_attach(dev); if (error) goto bad; return (0); bad: ipmi_release_resources(dev); return (error); } static device_method_t ipmi_methods[] = { /* Device interface */ DEVMETHOD(device_probe, ipmi_acpi_probe), DEVMETHOD(device_attach, ipmi_acpi_attach), DEVMETHOD(device_detach, ipmi_detach), { 0, 0 } }; static driver_t ipmi_acpi_driver = { "ipmi", ipmi_methods, sizeof(struct ipmi_softc), }; DRIVER_MODULE(ipmi_acpi, acpi, ipmi_acpi_driver, ipmi_devclass, 0, 0); MODULE_DEPEND(ipmi_acpi, acpi, 1, 1, 1); Index: head/sys/dev/sdhci/sdhci_acpi.c =================================================================== --- head/sys/dev/sdhci/sdhci_acpi.c (revision 339753) +++ head/sys/dev/sdhci/sdhci_acpi.c (revision 339754) @@ -1,413 +1,414 @@ /*- * Copyright (c) 2017 Oleksandr Tymoshenko * 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 ``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 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. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "mmcbr_if.h" #include "sdhci_if.h" static const struct sdhci_acpi_device { const char* hid; int uid; const char *desc; u_int quirks; } sdhci_acpi_devices[] = { { "80860F14", 1, "Intel Bay Trail/Braswell eMMC 4.5/4.5.1 Controller", SDHCI_QUIRK_INTEL_POWER_UP_RESET | SDHCI_QUIRK_WAIT_WHILE_BUSY | SDHCI_QUIRK_CAPS_BIT63_FOR_MMC_HS400 | SDHCI_QUIRK_PRESET_VALUE_BROKEN }, { "80860F14", 3, "Intel Bay Trail/Braswell SDXC Controller", SDHCI_QUIRK_WAIT_WHILE_BUSY | SDHCI_QUIRK_PRESET_VALUE_BROKEN }, { "80860F16", 0, "Intel Bay Trail/Braswell SDXC Controller", SDHCI_QUIRK_WAIT_WHILE_BUSY | SDHCI_QUIRK_PRESET_VALUE_BROKEN }, { "80865ACA", 0, "Intel Apollo Lake SDXC Controller", SDHCI_QUIRK_BROKEN_DMA | /* APL18 erratum */ SDHCI_QUIRK_WAIT_WHILE_BUSY | SDHCI_QUIRK_PRESET_VALUE_BROKEN }, { "80865ACC", 0, "Intel Apollo Lake eMMC 5.0 Controller", SDHCI_QUIRK_BROKEN_DMA | /* APL18 erratum */ SDHCI_QUIRK_INTEL_POWER_UP_RESET | SDHCI_QUIRK_WAIT_WHILE_BUSY | SDHCI_QUIRK_MMC_DDR52 | SDHCI_QUIRK_CAPS_BIT63_FOR_MMC_HS400 | SDHCI_QUIRK_PRESET_VALUE_BROKEN }, { "AMDI0040", 0, "AMD eMMC 5.0 Controller", SDHCI_QUIRK_32BIT_DMA_SIZE }, { NULL, 0, NULL, 0} }; static char *sdhci_ids[] = { "80860F14", "80860F16", "80865ACA", "80865ACC", "AMDI0040", NULL }; struct sdhci_acpi_softc { u_int quirks; /* Chip specific quirks */ struct resource *irq_res; /* IRQ resource */ void *intrhand; /* Interrupt handle */ struct sdhci_slot slot; struct resource *mem_res; /* Memory resource */ }; static void sdhci_acpi_intr(void *arg); static int sdhci_acpi_detach(device_t dev); static uint8_t sdhci_acpi_read_1(device_t dev, struct sdhci_slot *slot __unused, bus_size_t off) { struct sdhci_acpi_softc *sc = device_get_softc(dev); bus_barrier(sc->mem_res, 0, 0xFF, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); return bus_read_1(sc->mem_res, off); } static void sdhci_acpi_write_1(device_t dev, struct sdhci_slot *slot __unused, bus_size_t off, uint8_t val) { struct sdhci_acpi_softc *sc = device_get_softc(dev); bus_barrier(sc->mem_res, 0, 0xFF, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); bus_write_1(sc->mem_res, off, val); } static uint16_t sdhci_acpi_read_2(device_t dev, struct sdhci_slot *slot __unused, bus_size_t off) { struct sdhci_acpi_softc *sc = device_get_softc(dev); bus_barrier(sc->mem_res, 0, 0xFF, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); return bus_read_2(sc->mem_res, off); } static void sdhci_acpi_write_2(device_t dev, struct sdhci_slot *slot __unused, bus_size_t off, uint16_t val) { struct sdhci_acpi_softc *sc = device_get_softc(dev); bus_barrier(sc->mem_res, 0, 0xFF, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); bus_write_2(sc->mem_res, off, val); } static uint32_t sdhci_acpi_read_4(device_t dev, struct sdhci_slot *slot __unused, bus_size_t off) { struct sdhci_acpi_softc *sc = device_get_softc(dev); bus_barrier(sc->mem_res, 0, 0xFF, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); return bus_read_4(sc->mem_res, off); } static void sdhci_acpi_write_4(device_t dev, struct sdhci_slot *slot __unused, bus_size_t off, uint32_t val) { struct sdhci_acpi_softc *sc = device_get_softc(dev); bus_barrier(sc->mem_res, 0, 0xFF, BUS_SPACE_BARRIER_READ | BUS_SPACE_BARRIER_WRITE); bus_write_4(sc->mem_res, off, val); } static void sdhci_acpi_read_multi_4(device_t dev, struct sdhci_slot *slot __unused, bus_size_t off, uint32_t *data, bus_size_t count) { struct sdhci_acpi_softc *sc = device_get_softc(dev); bus_read_multi_stream_4(sc->mem_res, off, data, count); } static void sdhci_acpi_write_multi_4(device_t dev, struct sdhci_slot *slot __unused, bus_size_t off, uint32_t *data, bus_size_t count) { struct sdhci_acpi_softc *sc = device_get_softc(dev); bus_write_multi_stream_4(sc->mem_res, off, data, count); } static const struct sdhci_acpi_device * sdhci_acpi_find_device(device_t dev) { - const char *hid; + char *hid; int i, uid; ACPI_HANDLE handle; ACPI_STATUS status; + int rv; - hid = ACPI_ID_PROBE(device_get_parent(dev), dev, sdhci_ids); - if (hid == NULL) + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, sdhci_ids, &hid); + if (rv > 0) return (NULL); handle = acpi_get_handle(dev); status = acpi_GetInteger(handle, "_UID", &uid); if (ACPI_FAILURE(status)) uid = 0; for (i = 0; sdhci_acpi_devices[i].hid != NULL; i++) { if (strcmp(sdhci_acpi_devices[i].hid, hid) != 0) continue; if ((sdhci_acpi_devices[i].uid != 0) && (sdhci_acpi_devices[i].uid != uid)) continue; return (&sdhci_acpi_devices[i]); } return (NULL); } static int sdhci_acpi_probe(device_t dev) { const struct sdhci_acpi_device *acpi_dev; acpi_dev = sdhci_acpi_find_device(dev); if (acpi_dev == NULL) return (ENXIO); device_set_desc(dev, acpi_dev->desc); return (BUS_PROBE_DEFAULT); } static int sdhci_acpi_attach(device_t dev) { struct sdhci_acpi_softc *sc = device_get_softc(dev); int rid, err; const struct sdhci_acpi_device *acpi_dev; acpi_dev = sdhci_acpi_find_device(dev); if (acpi_dev == NULL) return (ENXIO); sc->quirks = acpi_dev->quirks; /* Allocate IRQ. */ rid = 0; sc->irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &rid, RF_ACTIVE); if (sc->irq_res == NULL) { device_printf(dev, "can't allocate IRQ\n"); return (ENOMEM); } rid = 0; sc->mem_res = bus_alloc_resource_any(dev, SYS_RES_MEMORY, &rid, RF_ACTIVE); if (sc->mem_res == NULL) { device_printf(dev, "can't allocate memory resource for slot\n"); sdhci_acpi_detach(dev); return (ENOMEM); } /* * Intel Bay Trail and Braswell eMMC controllers share the same IDs, * but while with these former DDR52 is affected by the VLI54 erratum, * these latter require the timeout clock to be hardcoded to 1 MHz. */ if (strcmp(acpi_dev->hid, "80860F14") == 0 && acpi_dev->uid == 1 && SDHCI_READ_4(dev, &sc->slot, SDHCI_CAPABILITIES) == 0x446cc8b2 && SDHCI_READ_4(dev, &sc->slot, SDHCI_CAPABILITIES2) == 0x00000807) sc->quirks |= SDHCI_QUIRK_MMC_DDR52 | SDHCI_QUIRK_DATA_TIMEOUT_1MHZ; sc->quirks &= ~sdhci_quirk_clear; sc->quirks |= sdhci_quirk_set; sc->slot.quirks = sc->quirks; err = sdhci_init_slot(dev, &sc->slot, 0); if (err) { device_printf(dev, "failed to init slot\n"); sdhci_acpi_detach(dev); return (err); } /* Activate the interrupt */ err = bus_setup_intr(dev, sc->irq_res, INTR_TYPE_MISC | INTR_MPSAFE, NULL, sdhci_acpi_intr, sc, &sc->intrhand); if (err) { device_printf(dev, "can't setup IRQ\n"); sdhci_acpi_detach(dev); return (err); } /* Process cards detection. */ sdhci_start_slot(&sc->slot); return (0); } static int sdhci_acpi_detach(device_t dev) { struct sdhci_acpi_softc *sc = device_get_softc(dev); if (sc->intrhand) bus_teardown_intr(dev, sc->irq_res, sc->intrhand); if (sc->irq_res) bus_release_resource(dev, SYS_RES_IRQ, rman_get_rid(sc->irq_res), sc->irq_res); if (sc->mem_res) { sdhci_cleanup_slot(&sc->slot); bus_release_resource(dev, SYS_RES_MEMORY, rman_get_rid(sc->mem_res), sc->mem_res); } return (0); } static int sdhci_acpi_shutdown(device_t dev) { return (0); } static int sdhci_acpi_suspend(device_t dev) { struct sdhci_acpi_softc *sc = device_get_softc(dev); int err; err = bus_generic_suspend(dev); if (err) return (err); sdhci_generic_suspend(&sc->slot); return (0); } static int sdhci_acpi_resume(device_t dev) { struct sdhci_acpi_softc *sc = device_get_softc(dev); int err; sdhci_generic_resume(&sc->slot); err = bus_generic_resume(dev); if (err) return (err); return (0); } static void sdhci_acpi_intr(void *arg) { struct sdhci_acpi_softc *sc = (struct sdhci_acpi_softc *)arg; sdhci_generic_intr(&sc->slot); } static device_method_t sdhci_methods[] = { /* device_if */ DEVMETHOD(device_probe, sdhci_acpi_probe), DEVMETHOD(device_attach, sdhci_acpi_attach), DEVMETHOD(device_detach, sdhci_acpi_detach), DEVMETHOD(device_shutdown, sdhci_acpi_shutdown), DEVMETHOD(device_suspend, sdhci_acpi_suspend), DEVMETHOD(device_resume, sdhci_acpi_resume), /* Bus interface */ DEVMETHOD(bus_read_ivar, sdhci_generic_read_ivar), DEVMETHOD(bus_write_ivar, sdhci_generic_write_ivar), /* mmcbr_if */ DEVMETHOD(mmcbr_update_ios, sdhci_generic_update_ios), DEVMETHOD(mmcbr_switch_vccq, sdhci_generic_switch_vccq), DEVMETHOD(mmcbr_tune, sdhci_generic_tune), DEVMETHOD(mmcbr_retune, sdhci_generic_retune), DEVMETHOD(mmcbr_request, sdhci_generic_request), DEVMETHOD(mmcbr_get_ro, sdhci_generic_get_ro), DEVMETHOD(mmcbr_acquire_host, sdhci_generic_acquire_host), DEVMETHOD(mmcbr_release_host, sdhci_generic_release_host), /* SDHCI accessors */ DEVMETHOD(sdhci_read_1, sdhci_acpi_read_1), DEVMETHOD(sdhci_read_2, sdhci_acpi_read_2), DEVMETHOD(sdhci_read_4, sdhci_acpi_read_4), DEVMETHOD(sdhci_read_multi_4, sdhci_acpi_read_multi_4), DEVMETHOD(sdhci_write_1, sdhci_acpi_write_1), DEVMETHOD(sdhci_write_2, sdhci_acpi_write_2), DEVMETHOD(sdhci_write_4, sdhci_acpi_write_4), DEVMETHOD(sdhci_write_multi_4, sdhci_acpi_write_multi_4), DEVMETHOD(sdhci_set_uhs_timing, sdhci_generic_set_uhs_timing), DEVMETHOD_END }; static driver_t sdhci_acpi_driver = { "sdhci_acpi", sdhci_methods, sizeof(struct sdhci_acpi_softc), }; static devclass_t sdhci_acpi_devclass; DRIVER_MODULE(sdhci_acpi, acpi, sdhci_acpi_driver, sdhci_acpi_devclass, NULL, NULL); MODULE_DEPEND(sdhci_acpi, sdhci, 1, 1, 1); #ifndef MMCCAM MMC_DECLARE_BRIDGE(sdhci_acpi); #endif Index: head/sys/dev/tpm/tpm_acpi.c =================================================================== --- head/sys/dev/tpm/tpm_acpi.c (revision 339753) +++ head/sys/dev/tpm/tpm_acpi.c (revision 339754) @@ -1,81 +1,82 @@ /* * Copyright (c) 2008, 2009 Michael Shalayeff * Copyright (c) 2009, 2010 Hans-Joerg Hoexer * All rights reserved. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER IN * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "tpmvar.h" #include "opt_acpi.h" #include #include #include char *tpm_ids[] = {"ATM1200", "BCM0102", "INTC0102", "SNO3504", "WEC1000", "PNP0C31", NULL}; static int tpm_acpi_probe(device_t dev) { - if (ACPI_ID_PROBE(device_get_parent(dev), dev, tpm_ids) != NULL) { - device_set_desc(dev, "Trusted Platform Module"); - return BUS_PROBE_DEFAULT; - } + int rv; - return ENXIO; + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, tpm_ids, NULL); + if (rv <= 0) + device_set_desc(dev, "Trusted Platform Module"); + + return (rv); } static device_method_t tpm_acpi_methods[] = { #if 0 /*In some case, TPM existence is found only in TPCA header*/ DEVMETHOD(device_identify, tpm_acpi_identify), #endif DEVMETHOD(device_probe, tpm_acpi_probe), DEVMETHOD(device_attach, tpm_attach), DEVMETHOD(device_detach, tpm_detach), DEVMETHOD(device_suspend, tpm_suspend), DEVMETHOD(device_resume, tpm_resume), { 0, 0 } }; static driver_t tpm_acpi_driver = { "tpm", tpm_acpi_methods, sizeof(struct tpm_softc), }; devclass_t tpm_devclass; DRIVER_MODULE(tpm, acpi, tpm_acpi_driver, tpm_devclass, 0, 0);