Recently got into NetBSD, and my laptop that otherwise best suited the hardware support of NetBSD (a 2015-era Intel Broadwell laptop) has a vestigial useless NVIDIA GeForce 950M card that frankly, was never good even at the time. It drains power and generates enough heat to give the laptop thermal problems. And unfortunately, NetBSD doesn't have any native ways to disable the card (which is automatically activated during the hardware detection at boot). I was able to get around this by using a NetBSD module provided in this mailing list post from 2016 by Bartek Krawczyk, but there are several steps to the process that aren't immediately clear so I thought I'd write it up.
This has been tested on NetBSD 11.0 running on X86-64 hardware, but will probably work on other NetBSD versions or architectures. My sample size is also all of one NVIDIA card.
Determining the ACPI call
In order to disable the NVIDIA card, we will need to determine what ACPI call is used to power on and off the card. This varies on a system by system basis, but is automatically discovered by the out-of-tree Linux bbswitch module. You may be able to determine this by reading the ACPI table generated by the acpidump tool on NetBSD itself, but it's a long, complicated file I couldn't make heads or tails of. I did this with an Xubuntu 24.04 Live USB image, but any Linux distribution that ships both bbswitch and acpi_call should work.
- Write out the Linux image to a USB stick (dd, Raspberry Pi Imaging tool, etc).
- Boot from the stick in your system you need to disable the card in and when the GRUB bootloader menu appears, hit the "e" button to edit the kernel command line. We need to disable the nouveau kernel module (the reverse-engineered open-source NVIDIA drivers) so that it does not assume control over the NVIDIA card and manage its power itself.
- Append
modprobe.blacklist=nouveau to the command line, then hit CTRL+X to boot.
- Make sure if there is an external power indicator for the NVIDIA card, it shows the card is powered on.
- When the system has booted into Linux, in a terminal install the acpi_call and bbswitch packages. This can be done on the Xubuntu image with
sudo apt install bbswitch-dkms acpi-call-dkms.
- Load the bbswitch module with
sudo modprobe bbswitch.
- Doing a
sudo su to switch to a root shell at this point is probably the easiest way to get the following commands to be run as root due to the piping involved, but there's probably a better way to do it.
- Turn off the NVIDIA card by running
echo OFF > /proc/acpi/bbswitch. See if any hardware power indicator if there is one shows the card has turned off.
- Turn back on the NVIDIA card by running
echo ON > /proc/acpi/bbswitch.
- Dump the kernel log with
dmesg and check for lines near the bottom that look like this:
[ 309.825455] bbswitch: Found integrated VGA device 0000:00:02.0: \_SB_.PCI0.GFX0
[ 309.825460] bbswitch: Found discrete VGA device 0000:01:00.0: \_SB_.PCI0.PEG0.PEGP
[ 309.825561] bbswitch: detected an Optimus _DSM function
- Record the one ACPI object listed as the discrete VGA device, in this case
\_SB_.PCI0.PEG0.PEGP.
- To determine the exact function, we are going to check with the acpi_call module allowing us to execute arbitrary APCI calls.
- Unload bbswitch with
rmmod bbswitch, then load acpi_call with modprobe acpi_call.
- Try disabling the card with the object and a
._OFF appended to the end it. In this case, \_SB.PCI0.PEG0.PEGP._OFF. The comamnd for this is echo "\_SB.PCI0.PEG0.PEGP._OFF" > /proc/acpi/call.
- If the card turns off, this is correct. If not, check the acpi_call sample script file for ideas on how to iterate on this to find the correct one.
- Try enabling the card with the same APCI object, but
._ON appended. In this case, \_SB.PCI0.PEG0.PEGP._ON, which is done as echo "\_SB.PCI0.PEG0.PEGP._ON" > /proc/acpi/call. Iterate on this if needed.
- Once you have determined these calls, you can shut down Linux.
Compiling a NetBSD kernel without Nouveau
We're going to need to be able to build a NetBSD kernel without Nouveau built in so nothing has control over the NVIDIA card. Disabling nouveau via boot.cfg might work, but I haven't gotten it to work in practice. I did this from a NetBSD 11 virtual machine running on KVM on Linux to have a more powerful system (and because the laptop with the NVIDIA card on was thermal throttling and emergency powering down trying to compile anything).
- Obtain the kernel source via the instructions in the NetBSD Guide here.
- If needed, build a NetBSD cross-compilation toolchain following the instructions here. This made things work smoother for me even under NetBSD, but may not be necessary there.
- Create a new kernel configuration by entering the configuration folder for your architecture ("sys/arch/amd64/conf/" for X86-64) and copying the "GENERIC" configuration file to a new name ("OPTIMUS"?).
- Disable the nouveau driver by adding a # in front of the following lines:
nouveau* at pci? dev ? function ?
nouveaufb* at nouveaufbbus?
- Build the kernel with this configuration following the NetBSD guide here.
- Copy the resulting "netbsd" file to the root folder of your target system, renaming the old one to "netbsd.old" in case of issues.
Compiling the Kernel Module to Power Off the NVIDIA card
- Under the NetBSD source tree used above, enter
sys/modules/ and create a folder named nvidia.
- In this file, create a file named
Makefile with the following contents:
.include "../Makefile.inc"
.PATH: ${S}/dev/acpi
KMOD= nvidia
SRCS= nvidia.c
WARNS= 4
.include <bsd.kmodule.mk>
- Edit the file
sys/dev/acpi/files.acpi in the source treee and add the following lines to the end of the file:
device nvidia
file dev/acpi/nvidia.c acpi
- In
sys/dev/acpi, add a file named nvidia.c with the following contents:
#include <sys/param.h>
#include <sys/module.h>
#include <dev/acpi/acpireg.h>
#include <dev/acpi/acpivar.h>
MODULE(MODULE_CLASS_DRIVER, nvidia, NULL);
#define NVIDIA_ON "\\_SB.PCI0.PEG1.GFX0._ON"
#define NVIDIA_OFF "\\_SB.PCI0.PEG1.GFX0._OFF"
/*
* Turns nVidia card off upon load and on upon unload.
*/
static int
nvidia_modcmd(modcmd_t cmd, void *aux)
{
ACPI_INTEGER val;
ACPI_STATUS rv;
switch (cmd) {
case MODULE_CMD_INIT:
rv = acpi_eval_integer(NULL, NVIDIA_OFF, &val);
if (ACPI_FAILURE(rv))
(void)printf("Failed to evaluate '%s': %s\n",
NVIDIA_OFF, AcpiFormatException(rv));
break;
case MODULE_CMD_FINI:
rv = acpi_eval_integer(NULL, NVIDIA_ON, &val);
if (ACPI_FAILURE(rv))
(void)printf("Failed to evaluate '%s': %s\n",
NVIDIA_OFF, AcpiFormatException(rv));
break;
default:
return ENOTTY;
}
return 0;
}
- Replace the contents of the
NVIDIA_ON and NVIDIA_OFF defines in the nvidia.c you just created with the ACPI commands determined earlier. Make sure to have an extra "\" at the start of the command as used in the module (I.E., it leads with two backslashes.)
- In
sys/modules/nvidia, run make (or the cross-compilation toolchain equivalent).
Using the Module
- Copy the contents of
sys/modules/nvidia to the target device. I put them under /opt/nvidia-disable.
- Load the module by passng the full path to the
modload command as root. In this case modload /opt/nvidia-disable/nvidia.kmod. The card should be disabled and power off.
- To re-enable the card for whatver reason, run
modunload nvidia.
Automating
I automated this by adding the modload command to /etc/rc.local. There may be better ways to do that, but that'll load the module at boot.
At some point it might be worth me trying to port bbswitch to NetBSD so this can happen in a much more generic and straightforward fashion, but that's a bit daunting for me at the moment given my lack of systems beyond the one to test this on.