init
This commit is contained in:
@@ -0,0 +1,25 @@
|
||||
CC ?= cc
|
||||
CFLAGS ?= -O2 -Wall -Wextra
|
||||
PREFIX ?= /usr/local
|
||||
BINDIR ?= $(PREFIX)/bin
|
||||
INSTALL ?= install
|
||||
|
||||
TARGET = nvbright
|
||||
SRC = nvbright.c
|
||||
|
||||
.PHONY: all clean install uninstall
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(SRC)
|
||||
$(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(SRC)
|
||||
|
||||
install: $(TARGET)
|
||||
$(INSTALL) -d $(DESTDIR)$(BINDIR)
|
||||
$(INSTALL) -m 755 $(TARGET) $(DESTDIR)$(BINDIR)/$(TARGET)
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(BINDIR)/$(TARGET)
|
||||
|
||||
clean:
|
||||
rm -f $(TARGET) *.o
|
||||
@@ -0,0 +1,95 @@
|
||||
# NVbright: directly control NVIDIA-based LCD backlight brightness on Apple MacBook Air A1370
|
||||
|
||||
NVbright is a lightweight utility for controlling LCD backlight brightness on NVIDIA-equipped Apple MacBooks running FreeBSD under EFI boot.
|
||||
|
||||
## About
|
||||
|
||||
On older NVIDIA-based Apple MacBooks (such as the MacBook Air A1370 with GeForce 320M, MCP89, or GT218 chipsets), LCD backlight level is modulated via a Pulse Width Modulation (PWM) signal generated by the GPU display engine (`PDISPLAY`).
|
||||
|
||||
Under macOS or BIOS compatibility mode, firmware dynamically alters backlight brightness through System Management Interrupts (SMI) triggered by legacy I/O port writes. However, when booting FreeBSD natively in EFI mode, this legacy SMI path is unavailable, leaving the screen backlight stuck or uncontrollable via standard ACPI methods.
|
||||
|
||||
`nvbright` addresses this by directly interacting with the GPU MMIO register BAR:
|
||||
|
||||
1. Identifies the NVIDIA VGA controller's physical MMIO BAR0 address dynamically using `/usr/sbin/pciconf`.
|
||||
2. Maps the register space into memory via `/dev/mem` using `mmap(2)`.
|
||||
3. Detects the active Serial Output Router (SOR) with an initialized PWM divisor (`PWM_DIV`).
|
||||
4. Reads and computes current duty cycle or writes new values latching the display engine registers (`PWM_CTL`).
|
||||
|
||||
The register mapping and control logic mirror the Linux `nouveau` driver implementation for NV50 and NVA3 chip architectures:
|
||||
|
||||
- `NV50_PDISP_SOR_PWM_DIV(i) = 0x0061c080 + i * 0x800`
|
||||
- `NV50_PDISP_SOR_PWM_CTL(i) = 0x0061c084 + i * 0x800`
|
||||
- `CTL_NEW (0x80000000)`: Latch/apply updated brightness value
|
||||
- `CTL_UNK (0x40000000)`: Required control flag on NVA3-class chipsets
|
||||
|
||||
## Build
|
||||
|
||||
`nvbright` is written in standard C and relies only on standard POSIX and FreeBSD system headers (`sys/mman.h`, `sys/types.h`, `fcntl.h`, `unistd.h`). No external dependencies or third-party libraries are required.
|
||||
|
||||
Compile using `make`:
|
||||
|
||||
```sh
|
||||
make
|
||||
```
|
||||
|
||||
Or compile manually using `clang` / `cc`:
|
||||
|
||||
```sh
|
||||
cc -O2 -Wall -Wextra -o nvbright nvbright.c
|
||||
```
|
||||
|
||||
To install the binary system-wide to `/usr/local/bin` (respects `PREFIX` and `DESTDIR`):
|
||||
|
||||
```sh
|
||||
sudo make install
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Because `nvbright` requires direct access to `/dev/mem` to map GPU MMIO registers and executes `/usr/sbin/pciconf`, it must be run as root (e.g., with `sudo` or `doas`).
|
||||
|
||||
### Command Syntax
|
||||
|
||||
```sh
|
||||
nvbright <get|set|up|down> [value 0-100]
|
||||
```
|
||||
|
||||
### Commands
|
||||
|
||||
- **`get`**: Queries the hardware and prints the current brightness level (percentage from 0 to 100), active SOR index, and PWM divisor.
|
||||
```sh
|
||||
sudo nvbright get
|
||||
```
|
||||
*Example Output:*
|
||||
```text
|
||||
Brightness: 65 / 100 (SOR1, pwm_div=1024)
|
||||
```
|
||||
|
||||
- **`set <0-100>`**: Sets the display backlight directly to the specified percentage value.
|
||||
```sh
|
||||
sudo nvbright set 75
|
||||
```
|
||||
*Example Output:*
|
||||
```text
|
||||
Set brightness: 75 / 100
|
||||
```
|
||||
|
||||
- **`up`**: Increases brightness by 10% (clamped at 100%).
|
||||
```sh
|
||||
sudo nvbright up
|
||||
```
|
||||
|
||||
- **`down`**: Decreases brightness by 10% (clamped at 0%).
|
||||
```sh
|
||||
sudo nvbright down
|
||||
```
|
||||
|
||||
### Desktop and Window Manager Integration
|
||||
|
||||
You can bind `nvbright up` and `nvbright down` commands to the brightness keys (`XF86MonBrightnessUp` and `XF86MonBrightnessDown`) in window manager configurations (such as Sway, i3, or Openbox) or configure `sudoers` / `doas.conf` rules to allow execution without password prompts.
|
||||
|
||||
## Credits
|
||||
|
||||
Created by Luxferre in 2026, released into the public domain with no warranties.
|
||||
|
||||
Hardware register specifications and control flow inspired by the Linux `nouveau` DRM backlight driver (`nv50` / `nva3`).
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* nvbright - NVIDIA GPU backlight control for Apple MacBooks (FreeBSD)
|
||||
*
|
||||
* On NVIDIA-based Apple hardware (e.g. MacBook Air A1370 / GeForce 320M,
|
||||
* MCP89 / GT218) the LCD backlight is driven by a PWM signal generated by
|
||||
* the GPU's display engine (PDISPLAY). The firmware normally reprograms it
|
||||
* through an SMI triggered by legacy I/O ports, but that path is dead under
|
||||
* FreeBSD (EFI boot), so we program the GPU MMIO PWM registers directly.
|
||||
* This mirrors the Linux nouveau nv50 / nva3 backlight driver:
|
||||
*
|
||||
* NV50_PDISP_SOR_PWM_DIV(i) = 0x0061c080 + i * 0x800
|
||||
* NV50_PDISP_SOR_PWM_CTL(i) = 0x0061c084 + i * 0x800
|
||||
* CTL_NEW = 0x80000000 (latch / apply new value)
|
||||
* CTL_UNK = 0x40000000 (required on nva3-class chips)
|
||||
*
|
||||
* The backlight SOR is located at runtime (the one with a programmed
|
||||
* PWM_DIV), and the GPU's MMIO register BAR is mapped via /dev/mem.
|
||||
* Requires root to run.
|
||||
*
|
||||
* Created by Luxferre in 2026, released into the public domain.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#define PWM_DIV(i) (0x0061c080UL + (i) * 0x800UL)
|
||||
#define PWM_CTL(i) (0x0061c084UL + (i) * 0x800UL)
|
||||
#define CTL_NEW 0x80000000u
|
||||
#define CTL_UNK 0x40000000u
|
||||
#define MAX_SOR 8
|
||||
#define STEP 10
|
||||
#define FIXED_DIV 1025u /* used only if no SOR has a programmed PWM_DIV */
|
||||
|
||||
static int g_memfd = -1;
|
||||
static volatile uint32_t *g_bar;
|
||||
static int g_sor = -1;
|
||||
static uint32_t g_div;
|
||||
|
||||
static void die(const char *msg) {
|
||||
fprintf(stderr, "nvbright: %s: %s\n", msg, strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static uint32_t rd(uint32_t off) { return g_bar[off / 4]; }
|
||||
static void wr(uint32_t off, uint32_t v) { g_bar[off / 4] = v; }
|
||||
|
||||
/* Locate the NVIDIA VGA device's MMIO register BAR (BAR0) via pciconf. */
|
||||
static void locate_bar(uint64_t *base, uint64_t *size) {
|
||||
FILE *fp = popen("/usr/sbin/pciconf -l -b", "r");
|
||||
if(!fp) die("popen /usr/sbin/pciconf");
|
||||
char line[1024];
|
||||
int in_nv = 0;
|
||||
*base = 0; *size = 0;
|
||||
while(fgets(line, sizeof(line), fp)) {
|
||||
if(line[0] != ' ' && line[0] != '\t') {
|
||||
in_nv = (strstr(line, "vendor=0x10de") &&
|
||||
(strstr(line, "class=0x030000") || strstr(line, "vgapci")));
|
||||
continue;
|
||||
}
|
||||
if(!in_nv) continue;
|
||||
if(strstr(line, "bar") && strstr(line, "[10]")) {
|
||||
char *b = strstr(line, "base");
|
||||
char *s = strstr(line, "size");
|
||||
unsigned long long bb = 0, sz = 0;
|
||||
if(b) sscanf(b, "base 0x%llx", &bb);
|
||||
if(s) sscanf(s, "size %llu", &sz);
|
||||
*base = (uint64_t)bb;
|
||||
*size = (uint64_t)sz;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pclose(fp);
|
||||
if(!*base) die("could not locate NVIDIA GPU MMIO BAR (is this an nvidia vga?)");
|
||||
}
|
||||
|
||||
/* Pick the SOR whose PWM the firmware already programmed (the backlight). */
|
||||
static void detect_sor(void) {
|
||||
for(int i = 0; i < MAX_SOR; i++) {
|
||||
uint32_t div = rd(PWM_DIV(i));
|
||||
if(div != 0) { g_sor = i; g_div = div; return; }
|
||||
}
|
||||
g_sor = 0; /* fallback: nv50-style fixed period on SOR0 */
|
||||
g_div = 0;
|
||||
}
|
||||
|
||||
static int bl_get(void) {
|
||||
uint32_t ctl = rd(PWM_CTL(g_sor));
|
||||
uint32_t val = ctl & 0x00ffffffu;
|
||||
uint32_t div = g_div ? g_div : FIXED_DIV;
|
||||
long lvl = ((long)val * 100 + (long)(div / 2)) / (long)div;
|
||||
if(lvl < 0) lvl = 0;
|
||||
if(lvl > 100) lvl = 100;
|
||||
return (int)lvl;
|
||||
}
|
||||
|
||||
static void bl_set(int level) {
|
||||
if(level < 0) level = 0;
|
||||
if(level > 100) level = 100;
|
||||
uint32_t div = g_div ? g_div : FIXED_DIV;
|
||||
uint32_t val = (uint32_t)(((uint64_t)level * div) / 100);
|
||||
wr(PWM_DIV(g_sor), div);
|
||||
wr(PWM_CTL(g_sor), val | CTL_NEW | CTL_UNK);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if(argc < 2) {
|
||||
fprintf(stderr, "Usage: %s <get|set|up|down> [value 0-100]\n", argv[0]);
|
||||
fprintf(stderr, " get print current brightness (0-100)\n");
|
||||
fprintf(stderr, " set <0-100> set brightness\n");
|
||||
fprintf(stderr, " up / down step brightness by %d\n", STEP);
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint64_t base = 0, size = 0;
|
||||
locate_bar(&base, &size);
|
||||
if(size == 0) size = 0x1000000UL;
|
||||
|
||||
g_memfd = open("/dev/mem", O_RDWR);
|
||||
if(g_memfd < 0) die("open /dev/mem (must run as root)");
|
||||
void *m = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, g_memfd, (off_t)base);
|
||||
if(m == MAP_FAILED) die("mmap /dev/mem");
|
||||
g_bar = (volatile uint32_t *)m;
|
||||
|
||||
detect_sor();
|
||||
|
||||
if(strcmp(argv[1], "get") == 0) {
|
||||
printf("Brightness: %d / 100 (SOR%d, pwm_div=%u)\n",
|
||||
bl_get(), g_sor, g_div ? g_div : FIXED_DIV);
|
||||
} else if(strcmp(argv[1], "set") == 0 && argc >= 3) {
|
||||
bl_set(atoi(argv[2]));
|
||||
printf("Set brightness: %d / 100\n", bl_get());
|
||||
} else if(strcmp(argv[1], "up") == 0) {
|
||||
int v = bl_get() + STEP; if(v > 100) v = 100;
|
||||
bl_set(v);
|
||||
printf("Brightness up: %d / 100\n", bl_get());
|
||||
} else if(strcmp(argv[1], "down") == 0) {
|
||||
int v = bl_get() - STEP; if(v < 0) v = 0;
|
||||
bl_set(v);
|
||||
printf("Brightness down: %d / 100\n", bl_get());
|
||||
} else {
|
||||
fprintf(stderr, "Unknown command: %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user