diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -14,6 +14,7 @@ MAN= aac.4 \ aacraid.4 \ acpi.4 \ + ${_acpi_apple.4} \ ${_acpi_asus.4} \ ${_acpi_asus_wmi.4} \ ${_acpi_dock.4} \ @@ -853,6 +854,10 @@ MLINKS+=${_wpi.4} ${_if_wpi.4} MLINKS+=xl.4 if_xl.4 +.if ${MACHINE_CPUARCH} == "amd64" +_acpi_apple.4= acpi_apple.4 +.endif + .if ${MACHINE_CPUARCH} == "amd64" || ${MACHINE_CPUARCH} == "i386" _acpi_asus.4= acpi_asus.4 _acpi_asus_wmi.4= acpi_asus_wmi.4 diff --git a/share/man/man4/acpi_apple.4 b/share/man/man4/acpi_apple.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/acpi_apple.4 @@ -0,0 +1,91 @@ +.\" Copyright (c) 2026 Abdelkader Boudih +.\" +.\" SPDX-License-Identifier: BSD-2-Clause +.\" +.Dd August 16, 2026 +.Dt ACPI_APPLE 4 +.Os +.Sh NAME +.Nm acpi_apple +.Nd Apple platform ACPI driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device acpi_apple" +.Cd "device backlight" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 Ns : +.Bd -literal -offset indent +acpi_apple_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver attaches to Apple platform ACPI devices found on Intel-based +Mac computers and provides two independent functions through a single +driver, selected by the ACPI hardware ID it matches: +.Bl -tag -width indent +.It APP9876 , APP000B +Platform instance. +Attaches to the PCH PMC companion +.Pq T2 Macs, 2018 and later +or the GMUX GPU multiplexer companion +.Pq Intel Macs with discrete graphics, 2006-2020 . +Arms Wake-on-LAN on shutdown for any interface with wake enabled +.Pq e.g. Va dev.bge.N.wake=1 , +exposes the lid switch state, and restores the backlight when the +lid is reopened. +.It APP0002 +Backlight instance. +Controls the panel backlight found on all Intel Macs with an internal +display, and registers with the +.Xr backlight 9 +framework. +The correct control method is selected automatically. +.El +.Pp +.Dv APP0001 +.Pq SMC companion +is intentionally not claimed by this driver; it is owned by +.Xr asmc 4 . +.Sh SYSCTL VARIABLES +The following variable is available under the platform instance's +device sysctl node +.Pq Va dev.acpi_apple.N : +.Bl -tag -width indent +.It Va dev.acpi_apple.N.lid +.Pq read-only +Current lid switch state: 1 if open, 0 if closed. +.El +.Sh HARDWARE +The +.Nm +driver supports the Apple ACPI devices found in the DSDT of Intel-based +Mac computers: +.Pp +.Bl -tag -width "APP9876" -compact +.It APP9876 +PCH PMC companion, present on T2 Macs +.Pq 2018 and later . +.It APP000B +GMUX GPU multiplexer, present on Intel Macs with discrete graphics +.Pq 2006-2020 , including single-GPU Mac minis. +.It APP0002 +Panel backlight, present on all Intel Macs with a built-in display. +.El +.Sh SEE ALSO +.Xr acpi 4 , +.Xr asmc 4 , +.Xr backlight 9 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 16.0 . +.Sh AUTHORS +.An Abdelkader Boudih Aq Mt freebsd@seuros.com diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -848,6 +848,9 @@ dev/alpm/alpm.c optional alpm pci dev/amdpm/amdpm.c optional amdpm pci | nfpm pci dev/amdsmb/amdsmb.c optional amdsmb pci +dev/apple_acpi/apple_acpi.c optional acpi_apple acpi pci backlight +dev/apple_acpi/apple_backlight.c optional acpi_apple acpi pci backlight +dev/apple_acpi/wake_on_lan.c optional acpi_apple acpi pci backlight # dev/aq/aq_main.c optional aq dev/aq/aq_media.c optional aq diff --git a/sys/dev/apple_acpi/apple_acpi.h b/sys/dev/apple_acpi/apple_acpi.h new file mode 100644 --- /dev/null +++ b/sys/dev/apple_acpi/apple_acpi.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _APPLE_ACPI_H_ +#define _APPLE_ACPI_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#define ACPI_APPLE_PLATFORM 0x01 /* WoL + lid (APP000B/APP9876) */ +#define ACPI_APPLE_BACKLIGHT 0x02 /* panel backlight (APP0002) */ + +/* + * Apple GMUX (GPU multiplexer) registers. + * On dual-GPU MacBook Pros, the GMUX owns the backlight PWM. + * Port offsets are relative to the GMUX resource base, consumed from + * the APP000B device's _CRS. + * + * Three I/O protocols exist: + * Classic (PIO) - pre-retina Macs: direct inb/outb at base+port. + * Indexed - retina Macs (2012-2015): write port number to + * GMUX_PORT_READ/WRITE, data at GMUX_PORT_VALUE. + * MMIO - T2 Macs: memory-mapped, select port+direction via + * GMUX_MMIO_PORT_SELECT, trigger with + * GMUX_MMIO_COMMAND_SEND, data at the port offset + * itself as a big-endian 32-bit value. + */ +#define GMUX_PORT_VERSION_MAJOR 0x04 +#define GMUX_PORT_VERSION_MINOR 0x05 +#define GMUX_PORT_VERSION_RELEASE 0x06 +#define GMUX_PORT_MAX_BRIGHTNESS 0x70 +#define GMUX_PORT_BRIGHTNESS 0x74 +#define GMUX_BRIGHTNESS_MASK 0x00ffffff + +/* Indexed GMUX registers */ +#define GMUX_PORT_VALUE 0xc2 +#define GMUX_PORT_READ 0xd0 +#define GMUX_PORT_WRITE 0xd4 + +/* + * Indexed-GMUX detection: write a fixed 3-byte unlock sequence to + * PORT_INDEXED_{MAGIC1,MAGIC2,MAGIC3}, then read the same two ports + * back and expect them to echo GMUX_INDEXED_SIGNATURE. + */ +#define GMUX_PORT_INDEXED_MAGIC1 0xcc +#define GMUX_PORT_INDEXED_MAGIC2 0xcd +#define GMUX_PORT_INDEXED_MAGIC3 0xce +#define GMUX_INDEXED_UNLOCK1 0xaa +#define GMUX_INDEXED_UNLOCK2 0x55 +#define GMUX_INDEXED_UNLOCK3 0x00 +#define GMUX_INDEXED_SIGNATURE 0x55aa + +/* MMIO GMUX registers */ +#define GMUX_MMIO_PORT_SELECT 0x0e +#define GMUX_MMIO_COMMAND_SEND 0x0f +#define GMUX_MMIO_READ 0x00 +#define GMUX_MMIO_WRITE 0x40 + +/* Minimum resource length needed to reach GMUX_PORT_BRIGHTNESS+4. */ +#define GMUX_MIN_IO_LEN (GMUX_PORT_BRIGHTNESS + 4) + +#define GMUX_TYPE_PIO 0 +#define GMUX_TYPE_INDEXED 1 +#define GMUX_TYPE_MMIO 2 + +struct apple_bl_reg { + uint32_t offset; + uint32_t mask; + uint8_t shift; +}; + +struct apple_bl_hw { + const char *name; + int bar; + vm_size_t mapsize; + struct apple_bl_reg ctrl; + struct apple_bl_reg enable; + uint32_t enable_val; + struct apple_bl_reg max_reg; + uint32_t max; +}; + +struct acpi_apple_softc { + device_t dev; + ACPI_HANDLE handle; + int mode; + + /* Backlight */ + struct mtx bl_mtx; /* protects hw access + cache */ + struct cdev *bl_cdev; + const struct apple_bl_hw *bl_hw; + void *bl_mmio; /* GPU BAR MMIO (non-GMUX) */ + uint16_t bl_gmux_base; /* GMUX I/O port base, 0=none */ + void *bl_gmux_mmio; /* GMUX MMIO base, NULL=none */ + uint8_t bl_gmux_type; /* GMUX_TYPE_{PIO,INDEXED,MMIO} */ + uint32_t bl_max; + uint32_t bl_brightness; /* cached desired percent 0..100 */ +}; + +/* wake_on_lan.c */ +int acpi_apple_platform_attach(struct acpi_apple_softc *sc); +void acpi_apple_platform_detach(struct acpi_apple_softc *sc); + +/* apple_backlight.c */ +int acpi_apple_backlight_attach(struct acpi_apple_softc *sc); +void acpi_apple_backlight_detach(struct acpi_apple_softc *sc); +void acpi_apple_backlight_restore(void); +void acpi_apple_backlight_off(void); +int acpi_apple_bl_update_status(device_t dev, + struct backlight_props *props); +int acpi_apple_bl_get_status(device_t dev, + struct backlight_props *props); +int acpi_apple_bl_get_info(device_t dev, + struct backlight_info *info); + +#endif /* _APPLE_ACPI_H_ */ diff --git a/sys/dev/apple_acpi/apple_acpi.c b/sys/dev/apple_acpi/apple_acpi.c new file mode 100644 --- /dev/null +++ b/sys/dev/apple_acpi/apple_acpi.c @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * acpi_apple(4) - FreeBSD driver for Apple platform ACPI devices. + * + * Handles multiple Apple ACPI devices with a single driver: + * + * APP000B (GMUX) / APP9876 (PCH PMC) - platform instance: + * - Wake-on-LAN: walk ACPI namespace on shutdown, call _PSW(1) on + * any device with ACPI_FLAG_WAKE_ENABLED (dev.X.Y.wake=1). + * - Lid state: expose dev.acpi_apple.N.lid sysctl via \_SB.LID0._LID. + * + * APP0002 (PNLF) - backlight instance: + * - Panel backlight control via GPU PCI BAR MMIO registers. + * - Supports Intel PCH/CPU, Nvidia NV40/NV50, AMD R600+ families. + * - Registers with backlight(9) framework. + * + * OSDW() gates on OSYS==0x2710 ("Darwin"), installed via + * hw.acpi.apple_darwin_osi=1. _PSW on GIGE only fires if the bge(4) + * instance has ACPI_FLAG_WAKE_ENABLED (dev.bge.X.wake=1). + */ + +#include +#include "opt_acpi.h" +#include "apple_acpi.h" + +#include "backlight_if.h" + +#define _COMPONENT ACPI_OEM +ACPI_MODULE_NAME("Apple") + +/* + * Attach to APP9876 (PCH PMC, T2 Macs), APP000B (GMUX, discrete GPU + * Macs), or APP0002 (panel backlight, all Intel Macs with display). + * APP0001 (SMC) is owned by asmc(4). + * + * BUS_PROBE_LOW_PRIORITY so a future apple_gmux driver can claim + * APP000B at default priority. + */ +static char *acpi_apple_ids[] = { "APP9876", "APP000B", "APP0002", NULL }; + +static int +acpi_apple_probe(device_t dev) +{ + int rv; + + if (acpi_disabled("apple")) + return (ENXIO); + rv = ACPI_ID_PROBE(device_get_parent(dev), dev, acpi_apple_ids, NULL); + if (rv > 0) + return (rv); + + if (acpi_MatchHid(acpi_get_handle(dev), "APP0002")) + device_set_desc(dev, "Apple Panel Backlight"); + else + device_set_desc(dev, "Apple platform controller"); + + return (BUS_PROBE_LOW_PRIORITY); +} + +static int +acpi_apple_attach(device_t dev) +{ + struct acpi_apple_softc *sc; + + sc = device_get_softc(dev); + sc->dev = dev; + sc->handle = acpi_get_handle(dev); + + if (acpi_MatchHid(sc->handle, "APP0002")) + sc->mode = ACPI_APPLE_BACKLIGHT; + else + sc->mode = ACPI_APPLE_PLATFORM; + + if (sc->mode == ACPI_APPLE_PLATFORM) + return (acpi_apple_platform_attach(sc)); + else + return (acpi_apple_backlight_attach(sc)); +} + +static int +acpi_apple_detach(device_t dev) +{ + struct acpi_apple_softc *sc; + + sc = device_get_softc(dev); + if (sc->mode == ACPI_APPLE_PLATFORM) + acpi_apple_platform_detach(sc); + else + acpi_apple_backlight_detach(sc); + return (0); +} + +static device_method_t acpi_apple_methods[] = { + DEVMETHOD(device_probe, acpi_apple_probe), + DEVMETHOD(device_attach, acpi_apple_attach), + DEVMETHOD(device_detach, acpi_apple_detach), + + DEVMETHOD(backlight_update_status, acpi_apple_bl_update_status), + DEVMETHOD(backlight_get_status, acpi_apple_bl_get_status), + DEVMETHOD(backlight_get_info, acpi_apple_bl_get_info), + + DEVMETHOD_END +}; + +static driver_t acpi_apple_driver = { + "acpi_apple", + acpi_apple_methods, + sizeof(struct acpi_apple_softc), +}; + +DRIVER_MODULE(acpi_apple, acpi, acpi_apple_driver, 0, 0); +MODULE_DEPEND(acpi_apple, acpi, 1, 1, 1); +MODULE_DEPEND(acpi_apple, backlight, 1, 1, 1); +MODULE_VERSION(acpi_apple, 1); diff --git a/sys/dev/apple_acpi/apple_backlight.c b/sys/dev/apple_acpi/apple_backlight.c new file mode 100644 --- /dev/null +++ b/sys/dev/apple_acpi/apple_backlight.c @@ -0,0 +1,783 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Apple panel backlight control. + * + * Two backends: GMUX (dual-GPU MacBook Pros, base+0x70/0x74 over PIO, + * indexed I/O, or MMIO depending on platform) and direct GPU PCI BAR + * MMIO (single-GPU Macs). GMUX is preferred whenever APP000B is + * present and its _CRS yields a working protocol. + */ + +#include +#include "apple_acpi.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "backlight_if.h" + +ACPI_SERIAL_DECL(apple, "Apple ACPI"); +static struct acpi_apple_softc *apple_backlight_sc; + +static const struct apple_bl_hw apple_bl_intel_pch = { + .name = "intel_pch", + .bar = 0, + .mapsize = 0xc8258, + .ctrl = { 0xc8254, 0x0000FFFF, 0 }, + .enable = { 0xc8250, 0xC0000000, 0 }, + .enable_val = 0xC0000000, + .max_reg = { 0xc8254, 0xFFFF0000, 16 }, + .max = 0xFFFF, +}; + +static const struct apple_bl_hw apple_bl_intel_cpu = { + .name = "intel_cpu", + .bar = 0, + .mapsize = 0xc8258, + .ctrl = { 0x48254, 0x0000FFFF, 0 }, + .enable = { 0x48250, 0xC0000000, 0 }, + .enable_val = 0xC0000000, + .max_reg = { 0xc8254, 0xFFFF0000, 16 }, + .max = 0xFFFF, +}; + +static const struct apple_bl_hw apple_bl_nvidia_nv40 = { + .name = "nvidia_nv40", + .bar = 0, + .mapsize = 0x15f4, + .ctrl = { 0x15f0, 0x001F0000, 16 }, + .max = 31, +}; + +static const struct apple_bl_hw apple_bl_nvidia_nv50 = { + .name = "nvidia_nv50", + .bar = 0, + .mapsize = 0x61c088, + .ctrl = { 0x61c084, 0x000007FF, 0 }, + .enable = { 0x61c084, 0x80000000, 0 }, + .enable_val = 0x80000000, + .max = 0x7FF, +}; + +static const struct apple_bl_hw apple_bl_amd_r600 = { + .name = "amd_r600", + .bar = 2, + .mapsize = 0x1730, + .ctrl = { 0x172c, 0x0000FF00, 8 }, + .max = 255, +}; + +/* DCE10 (Tonga/Fiji, e.g. R9 M395/M395X, R9 Nano). */ +static const struct apple_bl_hw apple_bl_amd_dce10 = { + .name = "amd_dce10", + .bar = 2, + .mapsize = 0x1208c, + .ctrl = { 0x12080, 0x0000FFFF, 0 }, + .enable = { 0x12080, 0x80000000, 0 }, + .enable_val = 0x80000000, + .max_reg = { 0x12088, 0x0000FFFF, 0 }, + .max = 0xFFFF, +}; + +struct apple_bl_match { + uint16_t vendor; + uint16_t dev_lo; + uint16_t dev_hi; + const struct apple_bl_hw *hw; +}; + +static const struct apple_bl_match apple_bl_matches[] = { + { 0x8086, 0x0100, 0x013f, &apple_bl_intel_cpu }, + { 0x8086, 0x0140, 0x017f, &apple_bl_intel_cpu }, + { 0x8086, 0, 0, &apple_bl_intel_pch }, + { 0x10de, 0x0040, 0x03ff, &apple_bl_nvidia_nv40 }, + { 0x10de, 0, 0, &apple_bl_nvidia_nv50 }, + { 0x1002, 0x6920, 0x6939, &apple_bl_amd_dce10 }, + { 0x1002, 0, 0, &apple_bl_amd_r600 }, + { 0, 0, 0, NULL }, +}; + +/* Classic (PIO) GMUX - pre-retina Macs. */ +static uint32_t +gmux_pio_read32(uint16_t base, uint8_t port) +{ + return (inl(base + port)); +} + +static void +gmux_pio_write32(uint16_t base, uint8_t port, uint32_t val) +{ + int i; + + for (i = 0; i < 4; i++) + outb(base + port + i, (val >> (i * 8)) & 0xff); +} + +/* Indexed GMUX - retina Macs (2012-2015). */ +static int +gmux_index_wait_ready(uint16_t base) +{ + int i; + uint8_t gwr; + + for (i = 0; i < 100; i++) { + gwr = inb(base + GMUX_PORT_WRITE); + if ((gwr & 0x01) == 0) + return (0); + inb(base + GMUX_PORT_READ); + DELAY(100); + } + return (ETIMEDOUT); +} + +static int +gmux_index_wait_complete(uint16_t base) +{ + int i; + uint8_t gwr; + + for (i = 0; i < 100; i++) { + gwr = inb(base + GMUX_PORT_WRITE); + if ((gwr & 0x01) != 0) { + /* Acknowledge completion. */ + inb(base + GMUX_PORT_READ); + return (0); + } + DELAY(100); + } + /* Clear stale status. */ + inb(base + GMUX_PORT_READ); + return (ETIMEDOUT); +} + +static uint32_t +gmux_index_read32(uint16_t base, uint8_t port) +{ + uint32_t val; + + gmux_index_wait_ready(base); + outb(base + GMUX_PORT_READ, port); + gmux_index_wait_complete(base); + val = inl(base + GMUX_PORT_VALUE); + return (val); +} + +static void +gmux_index_write32(uint16_t base, uint8_t port, uint32_t val) +{ + int i; + + for (i = 0; i < 4; i++) + outb(base + GMUX_PORT_VALUE + i, (val >> (i * 8)) & 0xff); + gmux_index_wait_ready(base); + outb(base + GMUX_PORT_WRITE, port); + gmux_index_wait_complete(base); +} + +/* MMIO GMUX - T2 Macs. */ +static inline uint8_t +gmux_mmio_read8(void *mmio, uint8_t port) +{ + return (*(volatile uint8_t *)((char *)mmio + port)); +} + +static inline void +gmux_mmio_write8(void *mmio, uint8_t port, uint8_t val) +{ + *(volatile uint8_t *)((char *)mmio + port) = val; +} + +static int +gmux_mmio_wait(void *mmio) +{ + int i; + + for (i = 0; i < 200; i++) { + if (gmux_mmio_read8(mmio, GMUX_MMIO_COMMAND_SEND) == 0) + return (0); + DELAY(100); + } + return (ETIMEDOUT); +} + +static uint32_t +gmux_mmio_read32(void *mmio, uint8_t port) +{ + uint32_t val; + + gmux_mmio_wait(mmio); + gmux_mmio_write8(mmio, GMUX_MMIO_PORT_SELECT, port); + gmux_mmio_write8(mmio, GMUX_MMIO_COMMAND_SEND, + GMUX_MMIO_READ | sizeof(val)); + gmux_mmio_wait(mmio); + val = be32toh(*(volatile uint32_t *)mmio); + return (val); +} + +static void +gmux_mmio_write32(void *mmio, uint8_t port, uint32_t val) +{ + gmux_mmio_wait(mmio); + *(volatile uint32_t *)mmio = htobe32(val); + gmux_mmio_write8(mmio, GMUX_MMIO_PORT_SELECT, port); + gmux_mmio_write8(mmio, GMUX_MMIO_COMMAND_SEND, + GMUX_MMIO_WRITE | sizeof(val)); + gmux_mmio_wait(mmio); +} + +static uint32_t +gmux_read32(struct acpi_apple_softc *sc, uint8_t port) +{ + if (sc->bl_gmux_type == GMUX_TYPE_MMIO) + return (gmux_mmio_read32(sc->bl_gmux_mmio, port)); + if (sc->bl_gmux_type == GMUX_TYPE_INDEXED) + return (gmux_index_read32(sc->bl_gmux_base, port)); + return (gmux_pio_read32(sc->bl_gmux_base, port)); +} + +static void +gmux_write32(struct acpi_apple_softc *sc, uint8_t port, uint32_t val) +{ + if (sc->bl_gmux_type == GMUX_TYPE_MMIO) + gmux_mmio_write32(sc->bl_gmux_mmio, port, val); + else if (sc->bl_gmux_type == GMUX_TYPE_INDEXED) + gmux_index_write32(sc->bl_gmux_base, port, val); + else + gmux_pio_write32(sc->bl_gmux_base, port, val); +} + +static inline bool +apple_bl_gmux_active(struct acpi_apple_softc *sc) +{ + return (sc->bl_gmux_base != 0 || sc->bl_gmux_mmio != NULL); +} + +static void +apple_bl_gmux_teardown(struct acpi_apple_softc *sc) +{ + if (sc->bl_gmux_mmio != NULL) { + pmap_unmapdev(sc->bl_gmux_mmio, GMUX_MIN_IO_LEN); + sc->bl_gmux_mmio = NULL; + } + sc->bl_gmux_base = 0; +} + +static inline uint32_t +apple_bl_read(struct acpi_apple_softc *sc, uint32_t offset) +{ + return (*(volatile uint32_t *)((char *)sc->bl_mmio + offset)); +} + +static inline void +apple_bl_write(struct acpi_apple_softc *sc, uint32_t offset, uint32_t val) +{ + *(volatile uint32_t *)((char *)sc->bl_mmio + offset) = val; +} + +static int +apple_bl_hw_get(struct acpi_apple_softc *sc) +{ + if (apple_bl_gmux_active(sc)) + return (gmux_read32(sc, + GMUX_PORT_BRIGHTNESS) & GMUX_BRIGHTNESS_MASK); + + return ((apple_bl_read(sc, sc->bl_hw->ctrl.offset) & + sc->bl_hw->ctrl.mask) >> sc->bl_hw->ctrl.shift); +} + +/* Resolve max brightness, enable the PWM if needed, cache brightness. */ +static void +apple_bl_mmio_init(struct acpi_apple_softc *sc, const struct apple_bl_hw *hw) +{ + uint32_t val; + + if (hw->max_reg.offset != 0) { + sc->bl_max = (apple_bl_read(sc, hw->max_reg.offset) & + hw->max_reg.mask) >> hw->max_reg.shift; + if (sc->bl_max == 0) + sc->bl_max = hw->max; + } else { + sc->bl_max = hw->max; + } + + if (hw->enable.offset != 0) { + val = apple_bl_read(sc, hw->enable.offset); + if ((val & hw->enable_val) != hw->enable_val) { + val |= hw->enable_val; + apple_bl_write(sc, hw->enable.offset, val); + } + } + + if (sc->bl_max > 0) + sc->bl_brightness = apple_bl_hw_get(sc) * 100 / sc->bl_max; +} + +static void +apple_bl_hw_set(struct acpi_apple_softc *sc, int level) +{ + uint32_t val; + + if (apple_bl_gmux_active(sc)) { + gmux_write32(sc, GMUX_PORT_BRIGHTNESS, + level & GMUX_BRIGHTNESS_MASK); + return; + } + + val = apple_bl_read(sc, sc->bl_hw->ctrl.offset); + val = (val & ~sc->bl_hw->ctrl.mask) | + ((level << sc->bl_hw->ctrl.shift) & sc->bl_hw->ctrl.mask); + apple_bl_write(sc, sc->bl_hw->ctrl.offset, val); +} + +static const struct apple_bl_hw * +apple_bl_find_gpu(device_t *gpu_devp) +{ + const struct apple_bl_match *m; + device_t gpu; + uint16_t vendor, device; + + gpu = pci_find_class(PCIC_DISPLAY, PCIS_DISPLAY_VGA); + if (gpu == NULL) + return (NULL); + + vendor = pci_get_vendor(gpu); + device = pci_get_device(gpu); + + for (m = apple_bl_matches; m->vendor != 0; m++) { + if (vendor != m->vendor) + continue; + if (m->dev_lo != 0 && + (device < m->dev_lo || device > m->dev_hi)) + continue; + *gpu_devp = gpu; + return (m->hw); + } + + return (NULL); +} + +static void * +apple_bl_map_bar(device_t dev, device_t gpu, const struct apple_bl_hw *hw) +{ + uint64_t bar_addr; + uint32_t bar_lo, bar_hi; + + bar_lo = pci_read_config(gpu, PCIR_BAR(hw->bar), 4); + + if (PCI_BAR_IO(bar_lo)) { + device_printf(dev, "BAR%d is I/O, expected memory\n", hw->bar); + return (NULL); + } + + if ((bar_lo & PCIM_BAR_MEM_TYPE) == PCIM_BAR_MEM_64) { + bar_hi = pci_read_config(gpu, PCIR_BAR(hw->bar) + 4, 4); + bar_addr = ((uint64_t)bar_hi << 32) | + (bar_lo & PCIM_BAR_MEM_BASE); + } else { + bar_addr = bar_lo & PCIM_BAR_MEM_BASE; + } + + if (bar_addr == 0) { + device_printf(dev, "BAR%d address is zero\n", hw->bar); + return (NULL); + } + + return (pmap_mapdev(bar_addr, hw->mapsize)); +} + +/* + * Probe for GMUX. Locate APP000B in the ACPI namespace by HID, consume + * its _CRS resource, and detect which I/O protocol the hardware speaks. + */ +#define GMUX_ACPI_HID "APP000B" + +struct apple_bl_gmux_crs { + uint16_t io_base; + uint32_t io_len; + uint64_t mem_base; +}; + +static ACPI_STATUS +apple_bl_gmux_found(ACPI_HANDLE handle, UINT32 level __unused, void *ctx, + void **retval __unused) +{ + *(ACPI_HANDLE *)ctx = handle; + return (AE_CTRL_TERMINATE); +} + +static ACPI_STATUS +apple_bl_gmux_crs_cb(ACPI_RESOURCE *res, void *ctx) +{ + struct apple_bl_gmux_crs *crs = ctx; + + switch (res->Type) { + case ACPI_RESOURCE_TYPE_IO: + crs->io_base = res->Data.Io.Minimum; + crs->io_len = res->Data.Io.AddressLength; + break; + case ACPI_RESOURCE_TYPE_FIXED_IO: + crs->io_base = res->Data.FixedIo.Address; + crs->io_len = res->Data.FixedIo.AddressLength; + break; + case ACPI_RESOURCE_TYPE_MEMORY32: + crs->mem_base = res->Data.Memory32.Minimum; + break; + case ACPI_RESOURCE_TYPE_FIXED_MEMORY32: + crs->mem_base = res->Data.FixedMemory32.Address; + break; + default: + break; + } + return (AE_OK); +} + +static bool +apple_bl_is_indexed(uint16_t base) +{ + uint16_t val; + + outb(base + GMUX_PORT_INDEXED_MAGIC1, GMUX_INDEXED_UNLOCK1); + outb(base + GMUX_PORT_INDEXED_MAGIC2, GMUX_INDEXED_UNLOCK2); + outb(base + GMUX_PORT_INDEXED_MAGIC3, GMUX_INDEXED_UNLOCK3); + + val = inb(base + GMUX_PORT_INDEXED_MAGIC1) | + ((uint16_t)inb(base + GMUX_PORT_INDEXED_MAGIC2) << 8); + return (val == GMUX_INDEXED_SIGNATURE); +} + +static bool +apple_bl_is_mmio(void *mmio) +{ + return (gmux_mmio_read8(mmio, GMUX_MMIO_COMMAND_SEND) != 0xff); +} + +static bool +apple_bl_find_gmux(struct acpi_apple_softc *sc) +{ + ACPI_HANDLE gmux_handle; + struct apple_bl_gmux_crs crs; + uint16_t base; + uint8_t ver_major, ver_minor, ver_release; + uint32_t version; + + gmux_handle = NULL; + AcpiGetDevices(GMUX_ACPI_HID, apple_bl_gmux_found, &gmux_handle, NULL); + if (gmux_handle == NULL) + return (false); + + bzero(&crs, sizeof(crs)); + if (ACPI_FAILURE(AcpiWalkResources(gmux_handle, "_CRS", + apple_bl_gmux_crs_cb, &crs))) + return (false); + + if (crs.io_base != 0 && crs.io_len >= GMUX_MIN_IO_LEN) { + base = crs.io_base; + + /* Try direct PIO first (classic/pre-retina GMUX). */ + ver_major = inb(base + GMUX_PORT_VERSION_MAJOR); + ver_minor = inb(base + GMUX_PORT_VERSION_MINOR); + ver_release = inb(base + GMUX_PORT_VERSION_RELEASE); + + if (ver_major != 0xff || ver_minor != 0xff) { + sc->bl_gmux_base = base; + sc->bl_gmux_type = GMUX_TYPE_PIO; + device_printf(sc->dev, + "GMUX v%d.%d.%d [classic] at 0x%x\n", + ver_major, ver_minor, ver_release, base); + return (true); + } + + /* Direct PIO returned 0xff - check for indexed GMUX. */ + if (apple_bl_is_indexed(base)) { + sc->bl_gmux_base = base; + sc->bl_gmux_type = GMUX_TYPE_INDEXED; + + version = gmux_index_read32(base, + GMUX_PORT_VERSION_MAJOR); + ver_major = (version >> 24) & 0xff; + ver_minor = (version >> 16) & 0xff; + ver_release = (version >> 8) & 0xff; + + device_printf(sc->dev, + "GMUX v%d.%d.%d [indexed] at 0x%x\n", + ver_major, ver_minor, ver_release, base); + return (true); + } + } + + /* No usable I/O resource - try the MMIO protocol (T2 GMUX). */ + if (crs.mem_base == 0) + return (false); + + sc->bl_gmux_mmio = pmap_mapdev(crs.mem_base, GMUX_MIN_IO_LEN); + if (sc->bl_gmux_mmio == NULL) + return (false); + + if (!apple_bl_is_mmio(sc->bl_gmux_mmio)) { + pmap_unmapdev(sc->bl_gmux_mmio, GMUX_MIN_IO_LEN); + sc->bl_gmux_mmio = NULL; + return (false); + } + + sc->bl_gmux_type = GMUX_TYPE_MMIO; + + version = gmux_mmio_read32(sc->bl_gmux_mmio, GMUX_PORT_VERSION_MAJOR); + ver_major = (version >> 24) & 0xff; + ver_minor = (version >> 16) & 0xff; + ver_release = (version >> 8) & 0xff; + + device_printf(sc->dev, "GMUX v%d.%d.%d [mmio] at %#jx\n", + ver_major, ver_minor, ver_release, (uintmax_t)crs.mem_base); + return (true); +} + +static int +acpi_apple_backlight_attach_gmux(struct acpi_apple_softc *sc) +{ + uint32_t raw_max; + + mtx_init(&sc->bl_mtx, "apple_bl", NULL, MTX_DEF); + + raw_max = gmux_read32(sc, GMUX_PORT_MAX_BRIGHTNESS) & + GMUX_BRIGHTNESS_MASK; + sc->bl_max = (raw_max > 0) ? raw_max : GMUX_BRIGHTNESS_MASK; + + if (sc->bl_max > 0) + sc->bl_brightness = apple_bl_hw_get(sc) * 100 / sc->bl_max; + + sc->bl_cdev = backlight_register("apple_bl", sc->dev); + if (sc->bl_cdev == NULL) { + device_printf(sc->dev, "cannot register backlight\n"); + apple_bl_gmux_teardown(sc); + mtx_destroy(&sc->bl_mtx); + return (ENXIO); + } + + device_printf(sc->dev, "gmux brightness %d/%d\n", + apple_bl_hw_get(sc), sc->bl_max); + + ACPI_SERIAL_BEGIN(apple); + apple_backlight_sc = sc; + ACPI_SERIAL_END(apple); + return (0); +} + +static void +acpi_apple_backlight_deferred(void *arg) +{ + struct acpi_apple_softc *sc = arg; + device_t gpu; + const struct apple_bl_hw *hw; + + hw = apple_bl_find_gpu(&gpu); + if (hw == NULL) { + device_printf(sc->dev, "no supported GPU found\n"); + return; + } + sc->bl_hw = hw; + + sc->bl_mmio = apple_bl_map_bar(sc->dev, gpu, hw); + if (sc->bl_mmio == NULL) + return; + + mtx_init(&sc->bl_mtx, "apple_bl", NULL, MTX_DEF); + apple_bl_mmio_init(sc, hw); + + sc->bl_cdev = backlight_register("apple_bl", sc->dev); + if (sc->bl_cdev == NULL) { + device_printf(sc->dev, "cannot register backlight\n"); + mtx_destroy(&sc->bl_mtx); + pmap_unmapdev(sc->bl_mmio, hw->mapsize); + sc->bl_mmio = NULL; + return; + } + + device_printf(sc->dev, "%s, brightness %d/%d\n", + hw->name, apple_bl_hw_get(sc), sc->bl_max); + + ACPI_SERIAL_BEGIN(apple); + apple_backlight_sc = sc; + ACPI_SERIAL_END(apple); +} + +static int +acpi_apple_backlight_attach_mmio(struct acpi_apple_softc *sc) +{ + device_t gpu; + const struct apple_bl_hw *hw; + + hw = apple_bl_find_gpu(&gpu); + if (hw == NULL) { + /* + * GPU not yet attached (ACPI bus enumerates before PCI). + * Defer the lookup until after all buses finish probing. + */ + config_intrhook_oneshot(acpi_apple_backlight_deferred, sc); + return (0); + } + sc->bl_hw = hw; + + sc->bl_mmio = apple_bl_map_bar(sc->dev, gpu, hw); + if (sc->bl_mmio == NULL) + return (ENXIO); + + mtx_init(&sc->bl_mtx, "apple_bl", NULL, MTX_DEF); + apple_bl_mmio_init(sc, hw); + + sc->bl_cdev = backlight_register("apple_bl", sc->dev); + if (sc->bl_cdev == NULL) { + device_printf(sc->dev, "cannot register backlight\n"); + mtx_destroy(&sc->bl_mtx); + pmap_unmapdev(sc->bl_mmio, hw->mapsize); + sc->bl_mmio = NULL; + return (ENXIO); + } + + device_printf(sc->dev, "%s, brightness %d/%d\n", + hw->name, apple_bl_hw_get(sc), sc->bl_max); + + ACPI_SERIAL_BEGIN(apple); + apple_backlight_sc = sc; + ACPI_SERIAL_END(apple); + return (0); +} + +int +acpi_apple_backlight_attach(struct acpi_apple_softc *sc) +{ + if (apple_bl_find_gmux(sc)) + return (acpi_apple_backlight_attach_gmux(sc)); + + return (acpi_apple_backlight_attach_mmio(sc)); +} + +void +acpi_apple_backlight_detach(struct acpi_apple_softc *sc) +{ + bool had_gmux; + + ACPI_SERIAL_BEGIN(apple); + if (apple_backlight_sc == sc) + apple_backlight_sc = NULL; + ACPI_SERIAL_END(apple); + + had_gmux = apple_bl_gmux_active(sc); + + if (sc->bl_cdev != NULL) + backlight_destroy(sc->bl_cdev); + if (sc->bl_mmio != NULL) + pmap_unmapdev(sc->bl_mmio, sc->bl_hw->mapsize); + apple_bl_gmux_teardown(sc); + if (sc->bl_cdev != NULL || sc->bl_mmio != NULL || had_gmux) + mtx_destroy(&sc->bl_mtx); +} + +static inline bool +apple_bl_ready(struct acpi_apple_softc *sc) +{ + return (sc->bl_hw != NULL || apple_bl_gmux_active(sc)); +} + +static inline int +apple_bl_pct_to_hw(struct acpi_apple_softc *sc, int pct) +{ + int level; + + level = pct * sc->bl_max / 100; + if (level > (int)sc->bl_max) + level = sc->bl_max; + return (level); +} + +int +acpi_apple_bl_update_status(device_t dev, struct backlight_props *props) +{ + struct acpi_apple_softc *sc; + + sc = device_get_softc(dev); + if (!apple_bl_ready(sc)) + return (ENXIO); + mtx_lock(&sc->bl_mtx); + apple_bl_hw_set(sc, apple_bl_pct_to_hw(sc, props->brightness)); + sc->bl_brightness = props->brightness; + mtx_unlock(&sc->bl_mtx); + + return (0); +} + +int +acpi_apple_bl_get_status(device_t dev, struct backlight_props *props) +{ + struct acpi_apple_softc *sc; + + sc = device_get_softc(dev); + if (!apple_bl_ready(sc)) + return (ENXIO); + props->brightness = sc->bl_brightness; + props->nlevels = 0; + + return (0); +} + +int +acpi_apple_bl_get_info(device_t dev, struct backlight_info *info) +{ + info->type = BACKLIGHT_TYPE_PANEL; + strlcpy(info->name, "Apple Panel Backlight", BACKLIGHTMAXNAMELENGTH); + return (0); +} + +/* + * Restore backlight to cached brightness. Called from the platform + * instance lid-open notify handler. Caller holds no locks. + */ +void +acpi_apple_backlight_restore(void) +{ + struct acpi_apple_softc *sc; + + ACPI_SERIAL_BEGIN(apple); + sc = apple_backlight_sc; + if (sc == NULL || !apple_bl_ready(sc)) { + ACPI_SERIAL_END(apple); + return; + } + mtx_lock(&sc->bl_mtx); + apple_bl_hw_set(sc, apple_bl_pct_to_hw(sc, sc->bl_brightness)); + mtx_unlock(&sc->bl_mtx); + ACPI_SERIAL_END(apple); +} + +/* + * Turn off backlight. Called from the platform instance lid-close + * notify handler. Does not change the cached brightness so that + * restore() brings it back on lid open. + */ +void +acpi_apple_backlight_off(void) +{ + struct acpi_apple_softc *sc; + + ACPI_SERIAL_BEGIN(apple); + sc = apple_backlight_sc; + if (sc == NULL || !apple_bl_ready(sc)) { + ACPI_SERIAL_END(apple); + return; + } + mtx_lock(&sc->bl_mtx); + apple_bl_hw_set(sc, 0); + mtx_unlock(&sc->bl_mtx); + ACPI_SERIAL_END(apple); +} diff --git a/sys/dev/apple_acpi/wake_on_lan.c b/sys/dev/apple_acpi/wake_on_lan.c new file mode 100644 --- /dev/null +++ b/sys/dev/apple_acpi/wake_on_lan.c @@ -0,0 +1,163 @@ +/* + * Copyright (c) 2026 Abdelkader Boudih + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +/* + * Apple platform ACPI: Wake-on-LAN and lid state. + * + * Walks the ACPI namespace on shutdown calling _PSW(1) on any device + * with ACPI_FLAG_WAKE_ENABLED set, arming EC.EWOL for wake on magic + * packet. Also exposes lid state via sysctl. + */ + +#include +#include "apple_acpi.h" + +/* + * APP9876 and APP000B attach concurrently as ACPI_APPLE_PLATFORM on T2 + * Macs with a discrete GPU. Refcount the shutdown hook and lid notify + * handler here so the second instance's attach/detach doesn't reinstall + * or rip out the first instance's hooks. + */ +static struct mtx acpi_apple_platform_mtx; +MTX_SYSINIT(acpi_apple_platform_mtx_init, &acpi_apple_platform_mtx, + "apple_platform", MTX_DEF); +static int acpi_apple_platform_refs; +static eventhandler_tag acpi_apple_shutdown_tag; +static ACPI_HANDLE acpi_apple_lid_handle; + +static ACPI_STATUS +acpi_apple_psw_cb(ACPI_HANDLE handle, UINT32 level __unused, void *context, + void **retval __unused) +{ + ACPI_HANDLE psw_handle; + device_t dev; + int enable; + + enable = (int)(uintptr_t)context; + + if (ACPI_FAILURE(AcpiGetHandle(handle, "_PSW", &psw_handle))) + return (AE_OK); + + dev = acpi_get_device(handle); + if (dev == NULL) + return (AE_OK); + + if (enable && (acpi_get_flags(dev) & ACPI_FLAG_WAKE_ENABLED) == 0) + return (AE_OK); + + (void)acpi_SetInteger(handle, "_PSW", enable); + if (bootverbose) + device_printf(dev, "acpi_apple: _PSW(%d) on %s\n", + enable, acpi_name(handle)); + + return (AE_OK); +} + +static void +acpi_apple_arm_wol(void *arg __unused, int howto __unused) +{ + AcpiWalkNamespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, + ACPI_UINT32_MAX, acpi_apple_psw_cb, NULL, + (void *)(uintptr_t)1, NULL); +} + +#define ACPI_NOTIFY_STATUS_CHANGED 0x80 + +static int +acpi_apple_lid_sysctl(SYSCTL_HANDLER_ARGS) +{ + ACPI_HANDLE lid_handle; + int val; + + val = 0; + if (ACPI_SUCCESS(AcpiGetHandle(ACPI_ROOT_OBJECT, + "\\_SB.LID0", &lid_handle))) + acpi_GetInteger(lid_handle, "_LID", &val); + return (sysctl_handle_int(oidp, &val, 0, req)); +} + +/* + * Deferred lid event handler. Runs outside ACPI notify context + * via AcpiOsExecute(). Evaluate _LID and restore backlight on open. + */ +static void +acpi_apple_lid_event(void *context __unused) +{ + int lid_open; + + lid_open = 0; + if (acpi_apple_lid_handle != NULL) + acpi_GetInteger(acpi_apple_lid_handle, "_LID", &lid_open); + + if (lid_open) + acpi_apple_backlight_restore(); + else + acpi_apple_backlight_off(); +} + +static void +acpi_apple_lid_notify(ACPI_HANDLE h __unused, UINT32 notify, + void *context __unused) +{ + if (notify == ACPI_NOTIFY_STATUS_CHANGED) + AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_apple_lid_event, NULL); +} + +int +acpi_apple_platform_attach(struct acpi_apple_softc *sc) +{ + struct sysctl_ctx_list *sctx; + struct sysctl_oid *tree; + + sctx = device_get_sysctl_ctx(sc->dev); + tree = device_get_sysctl_tree(sc->dev); + + SYSCTL_ADD_PROC(sctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "lid", CTLTYPE_INT | CTLFLAG_RD | CTLFLAG_MPSAFE, + sc->dev, 0, acpi_apple_lid_sysctl, "I", + "Lid open (1) or closed (0)"); + + mtx_lock(&acpi_apple_platform_mtx); + if (acpi_apple_platform_refs++ == 0) { + acpi_apple_shutdown_tag = EVENTHANDLER_REGISTER( + shutdown_final, acpi_apple_arm_wol, NULL, + SHUTDOWN_PRI_LAST); + + /* Install lid notify handler for backlight restore. */ + if (ACPI_SUCCESS(AcpiGetHandle(ACPI_ROOT_OBJECT, + "\\_SB.LID0", &acpi_apple_lid_handle))) { + if (ACPI_SUCCESS(AcpiInstallNotifyHandler( + acpi_apple_lid_handle, ACPI_DEVICE_NOTIFY, + acpi_apple_lid_notify, NULL))) + device_printf(sc->dev, + "lid notify handler installed\n"); + else + acpi_apple_lid_handle = NULL; + } + } + mtx_unlock(&acpi_apple_platform_mtx); + + return (0); +} + +void +acpi_apple_platform_detach(struct acpi_apple_softc *sc __unused) +{ + mtx_lock(&acpi_apple_platform_mtx); + if (--acpi_apple_platform_refs == 0) { + if (acpi_apple_lid_handle != NULL) { + AcpiRemoveNotifyHandler(acpi_apple_lid_handle, + ACPI_DEVICE_NOTIFY, acpi_apple_lid_notify); + acpi_apple_lid_handle = NULL; + } + if (acpi_apple_shutdown_tag != NULL) { + EVENTHANDLER_DEREGISTER(shutdown_final, + acpi_apple_shutdown_tag); + acpi_apple_shutdown_tag = NULL; + } + } + mtx_unlock(&acpi_apple_platform_mtx); +} diff --git a/sys/modules/acpi/Makefile b/sys/modules/acpi/Makefile --- a/sys/modules/acpi/Makefile +++ b/sys/modules/acpi/Makefile @@ -16,6 +16,11 @@ aibs .endif +.if ${MACHINE_CPUARCH} == "amd64" +SUBDIR+= \ + acpi_apple +.endif + SUBDIR+= \ acpi_einj \ acpi_ged diff --git a/sys/modules/acpi/acpi_apple/Makefile b/sys/modules/acpi/acpi_apple/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/acpi/acpi_apple/Makefile @@ -0,0 +1,8 @@ +.PATH: ${SRCTOP}/sys/dev/apple_acpi + +KMOD= acpi_apple +SRCS= apple_acpi.c wake_on_lan.c apple_backlight.c \ + opt_acpi.h acpi_if.h bus_if.h device_if.h \ + pci_if.h backlight_if.h + +.include