acpi_apple: Apple platform ACPI driver,

handles wake-on-LAN arming and lid state on the PCH
PMC/GMUX platform instance, and panel backlight
control on the PNLF instance, registering with
backlight(9).

Reviewed by:	ziaee, adrian
Differential Revision:	https://reviews.freebsd.org/D58867
This commit is contained in:
Abdelkader Boudih committed 2026-10-15 14:03:22 +01:00
1 parent 802f5e4d69
commit d7d237ce73
9 files changed
+1255

No files matched your search

+2
View File
@@ -14,6 +14,7 @@ MANPACKAGE= kernel-man
MAN= aac.4 \
aacraid.4 \
acpi.4 \
${_acpi_apple.4} \
${_acpi_asus.4} \
${_acpi_asus_wmi.4} \
${_acpi_dock.4} \
@@ -956,6 +957,7 @@ _xnb.4= xnb.4
.endif
.if ${MACHINE_CPUARCH} == "amd64"
_acpi_apple.4= acpi_apple.4
_ioat.4= ioat.4
_iwx.4= iwx.4
_nvdimm.4= nvdimm.4
+74
View File
@@ -0,0 +1,74 @@
.\"
.\" Copyright (c) 2026 Abdelkader Boudih <[email protected]>
.\"
.\" SPDX-License-Identifier: BSD-2-Clause
.\"
.Dd August 16, 2026
.Dt ACPI_APPLE 4 amd64
.Os
.Sh NAME
.Nm acpi_apple
.Nd Apple platform ACPI driver
.Sh SYNOPSIS
.Cd device acpi_apple
.Cd device backlight
.Sh DESCRIPTION
The
.Nm
driver attaches to Apple platform ACPI devices found on Intel-based
Macs 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 ,
and follows the ACPI lid switch, blanking the backlight when the lid is
closed and restoring it 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 HARDWARE
The
.Nm
driver supports the Apple ACPI devices found in the DSDT of Intel-based
Macs:
.Pp
.Bl -bullet -compact
.It
APP9876, the PCH PMC companion, present on T2 Macs
.Pq 2018 and later .
.It
APP000B, the GMUX GPU multiplexer, present on Intel Macs with discrete
graphics
.Pq 2006-2020 ,
including single-GPU Mac minis.
.It
APP0002, the 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 [email protected]
+3
View File
@@ -848,6 +848,9 @@ dev/ale/if_ale.c optional ale pci
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/apple_platform.c optional acpi_apple acpi pci backlight
#
dev/aq/aq_main.c optional aq
dev/aq/aq_media.c optional aq
+120
View File
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
*
* 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: follow acpi_lid_event to blank and restore the
* backlight.
*
* 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 <sys/cdefs.h>
#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);
+149
View File
@@ -0,0 +1,149 @@
/*
* Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#ifndef _APPLE_ACPI_H_
#define _APPLE_ACPI_H_
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/sysctl.h>
#include <contrib/dev/acpica/include/acpi.h>
#include <dev/acpica/acpivar.h>
#include <dev/backlight/backlight.h>
#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 resource base
* 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
/* A version read returns major, minor and release packed into 32 bits. */
#define GMUX_VERSION_MAJOR(v) (((v) >> 24) & 0xff)
#define GMUX_VERSION_MINOR(v) (((v) >> 16) & 0xff)
#define GMUX_VERSION_RELEASE(v) (((v) >> 8) & 0xff)
/*
* An absent GMUX floats the bus high, so a read of all ones means the
* protocol under test is not the one this machine speaks.
*/
#define GMUX_FLOATING 0xff
/* Handshake polling: microseconds per iteration, and iteration limits. */
#define GMUX_POLL_DELAY 100
#define GMUX_INDEXED_RETRIES 100
#define GMUX_MMIO_RETRIES 200
/* Indexed GMUX registers */
#define GMUX_PORT_VALUE 0xc2
#define GMUX_PORT_READ 0xd0
#define GMUX_PORT_WRITE 0xd4
/* Bit 0 of GMUX_PORT_WRITE: command outstanding, cleared once complete. */
#define GMUX_INDEXED_BUSY 0x01
/*
* 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_* */
uint32_t bl_max;
uint32_t bl_brightness; /* cached percent 0..100 */
};
/* apple_platform.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_ */
+771
View File
@@ -0,0 +1,771 @@
/*
* Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
*
* 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 <sys/cdefs.h>
#include "apple_acpi.h"
#include <sys/endian.h>
#include <sys/systm.h>
#include <dev/backlight/backlight.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
#include <machine/cpufunc.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#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));
}
/* A 32-bit GMUX write goes out as four byte writes, low byte first. */
static void
gmux_pio_write32_at(uint16_t addr, uint32_t val)
{
for (u_int i = 0; i < sizeof(val); i++)
outb(addr + i, (uint8_t)(val >> (i * NBBY)));
}
static void
gmux_pio_write32(uint16_t base, uint8_t port, uint32_t val)
{
gmux_pio_write32_at(base + port, val);
}
/* Indexed GMUX - Retina Macs (2012-2015). */
static int
gmux_index_wait_ready(uint16_t base)
{
uint8_t gwr;
for (int i = 0; i < GMUX_INDEXED_RETRIES; i++) {
gwr = inb(base + GMUX_PORT_WRITE);
if ((gwr & GMUX_INDEXED_BUSY) == 0)
return (0);
inb(base + GMUX_PORT_READ);
DELAY(GMUX_POLL_DELAY);
}
return (ETIMEDOUT);
}
static int
gmux_index_wait_complete(uint16_t base)
{
uint8_t gwr;
for (int i = 0; i < GMUX_INDEXED_RETRIES; i++) {
gwr = inb(base + GMUX_PORT_WRITE);
if ((gwr & GMUX_INDEXED_BUSY) != 0) {
/* Acknowledge completion. */
inb(base + GMUX_PORT_READ);
return (0);
}
DELAY(GMUX_POLL_DELAY);
}
/* 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)
{
gmux_pio_write32_at(base + GMUX_PORT_VALUE, val);
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)
{
for (int i = 0; i < GMUX_MMIO_RETRIES; i++) {
if (gmux_mmio_read8(mmio, GMUX_MMIO_COMMAND_SEND) == 0)
return (0);
DELAY(GMUX_POLL_DELAY);
}
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) !=
GMUX_FLOATING);
}
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 != GMUX_FLOATING ||
ver_minor != GMUX_FLOATING) {
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);
}
/* The bus floated high - 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 = GMUX_VERSION_MAJOR(version);
ver_minor = GMUX_VERSION_MINOR(version);
ver_release = GMUX_VERSION_RELEASE(version);
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 = GMUX_VERSION_MAJOR(version);
ver_minor = GMUX_VERSION_MINOR(version);
ver_release = GMUX_VERSION_RELEASE(version);
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 int
apple_bl_attach_gpu(struct acpi_apple_softc *sc, device_t gpu,
const struct apple_bl_hw *hw)
{
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);
}
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;
}
(void)apple_bl_attach_gpu(sc, gpu, hw);
}
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);
}
return (apple_bl_attach_gpu(sc, gpu, hw));
}
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);
}
+123
View File
@@ -0,0 +1,123 @@
/*
* Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
*
* 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 follows acpi_lid(4) to blank and restore the panel
* backlight.
*/
#include <sys/cdefs.h>
#include "apple_acpi.h"
/*
* APP9876 and APP000B attach concurrently as ACPI_APPLE_PLATFORM on T2
* Macs with a discrete GPU. Refcount the shutdown and lid hooks 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 eventhandler_tag acpi_apple_lid_tag;
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);
}
/*
* The GMUX handshake polls for milliseconds, so keep it out of the
* acpi_lid(4) notify path, which runs with ACPI_SERIAL(lid) held.
*/
static void
acpi_apple_backlight_task(void *context)
{
if (context != NULL)
acpi_apple_backlight_restore();
else
acpi_apple_backlight_off();
}
static void
acpi_apple_lid_event(void *arg __unused, int lid_open)
{
AcpiOsExecute(OSL_NOTIFY_HANDLER, acpi_apple_backlight_task,
(void *)(uintptr_t)lid_open);
}
int
acpi_apple_platform_attach(struct acpi_apple_softc *sc __unused)
{
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);
acpi_apple_lid_tag = EVENTHANDLER_REGISTER(acpi_lid_event,
acpi_apple_lid_event, NULL, EVENTHANDLER_PRI_ANY);
}
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);
KASSERT(acpi_apple_platform_refs > 0,
("acpi_apple: platform refcount underflow"));
if (acpi_apple_platform_refs > 0 && --acpi_apple_platform_refs == 0) {
if (acpi_apple_lid_tag != NULL) {
EVENTHANDLER_DEREGISTER(acpi_lid_event,
acpi_apple_lid_tag);
acpi_apple_lid_tag = 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);
}
+5
View File
@@ -16,6 +16,11 @@ SUBDIR= \
aibs
.endif
.if ${MACHINE_CPUARCH} == "amd64"
SUBDIR+= \
acpi_apple
.endif
SUBDIR+= \
acpi_einj \
acpi_ged
+8
View File
@@ -0,0 +1,8 @@
.PATH: ${SRCTOP}/sys/dev/apple_acpi
KMOD= acpi_apple
SRCS= apple_acpi.c apple_platform.c apple_backlight.c \
opt_acpi.h acpi_if.h bus_if.h device_if.h \
pci_if.h backlight_if.h
.include <bsd.kmod.mk>