A while ago, I rescued a 2011 11-inch MacBook Air (MacBookAir4,1).
Someone mentioned having success installing FreeBSD on this model of laptop, so I had to
try it for myself. To my surprise, with some fiddling, I got it working fairly well,
and FreeBSD comes with drivers for even some of the more unusual and proprietary components
Apple used during this era. I'm writing my installation notes here in case someone else
wants to give it a shot on their early-2010s Intel MacBook as well. Even with only 2 GB
of RAM and a 15-year-old dual-core CPU, this machine has been working well for me for
web browsing and light hobby development, and the small size and lightness of these 11-inch
MacBook Airs is still hard to beat.
Initial install
Out of the box, FreeBSD works well enough to provide a text console and keyboard with the default
install settings. However, wifi won't work without rebuilding the kernel and installing
a package, so I installed the system source and ports tree as part of the initial install.
In order to get online long enough to download and install the necessary packages to
get wifi going, I was able to use an Apple Thunderbolt Ethernet adapter, which FreeBSD
supports out of the box with its
bge driver.
Wifi
This model of MacBook Air contains a Broadcom BCM43224 wifi chipset. FreeBSD supports
this chipset with its
bwn driver, but this support is not compiled into the
kernel by default for license reasons. Building a custom kernel is time-consuming, but
easy enough, following the instructions
in
the FreeBSD handbook. I took the
GENERIC kernel configuration and added one
line:
options BWN_GPL_PHY
and rebuilt the kernel. (However, I did
not uncomment the
device bwn line
to link the driver into the kernel directly; keeping it in a module is handy for power
management as I'll discuss below.) I also installed the
bwn-firmware-kmod package
and configured the firmware to be loaded at boot by adding to
/boot/loader.conf:
bwn_v4_n_ucode_load="YES"
and configured the wlan interface in
/etc/rc.conf:
wlans_bwn0="wlan"
ifconfig_wlan="WPA SYNCDHCP"
and put my wifi credentials in
/etc/wpa_supplicant.conf as described
in the
FreeBSD handbook. With that, I was able to reboot and connect to wifi, and I could
put the Thunderbolt dongle away.
Trackpad
FreeBSD supports Apple's fancy multi-touch trackpads, including
two-finger scrolling and right-clicking, using its
wsp driver. However,
without tweaks, it appears that the generic USB mouse driver
ums takes
precedence, which only supports the trackpad with basic cursor movement with one button.
There's probably a better way to do this, but to get the
wsp driver to take
effect, I had to disable the
ums driver in
/boot/loader.conf with:
hint.ums.0.disabled="1"
and load the drivers during system startup by adding to
/etc/rc.conf:
kld_list="wsp"
moused_port="wsp0"
moused_enable="YES"
Enabling
moused appears to be necessary, since without it running, then I
was seeing erratic keyboard input, and neither Xorg nor Wayland would respond to the
trackpad.
Power management and sleep/wake
Power management works well for the most part, once all of the proper hardware drivers
are loaded. (Without all of these drivers loaded, not so much.) I set the
asmc driver to load at boot time for the Apple system
management controller by adding to
/boot/loader.conf:
asmc_load="YES"
I also installed the
drm-kmod package for video drivers, and set the
i915kms driver to load during system startup by adding it to the
kld_list
in
/etc/rc.conf:
kld_list="i915kms wsp"
With the SMC and graphics drivers loaded, I can successfully suspend and resume the
laptop by closing and opening the lid by adding to
/etc/sysctl.conf:
hw.acpi.lid_switch_state=S3
However, the
bwn driver struggles to reinitialize after waking on its own.
I was nonetheless able to manually get wifi working again by unloading and reloading
the kernel module for the driver. I automated this by editing the
/etc/rc.suspend
and
/etc/rc.resume scripts. In
rc.suspend, I unload the driver immediately
before the kernel enters sleep:
### ... the rest of the script runs ...
for _rc_elem in $files; do
debug "run_rc_script $_rc_elem suspend"
run_rc_script $_rc_elem suspend
done
### LOCAL EDIT: unload bwn so it can be reloaded after resume
kldunload if_bwn
### LOCAL EDIT ends
/usr/bin/logger -t $subsystem suspend at `/bin/date +'%Y%m%d %H:%M:%S'`
/bin/sync && /bin/sync && /bin/sync
/bin/sleep 3
/bin/rm -f /var/run/rc.suspend.pid
if [ $subsystem = "apm" ]; then
/usr/sbin/zzz
else
# Notify the kernel to continue the suspend process
/usr/sbin/acpiconf -k 0
fi
exit 0
In
rc.resume, I reload the driver early:
### ... rest of script ...
if [ -r /var/run/rc.suspend.pid ]; then
kill -9 `cat /var/run/rc.suspend.pid`
/bin/rm -f /var/run/rc.suspend.pid
echo 'rc.resume: killed rc.suspend that was still around'
fi
# If a device driver has problems resuming, try unloading it before
# suspend and reloading it on resume. Example:
### LOCAL EDIT
kldload if_bwn
### LOCAL EDIT END
/usr/bin/logger -t $subsystem resumed at `/bin/date +'%Y%m%d %H:%M:%S'`
/bin/sync && /bin/sync && /bin/sync
### ... rest of script ...
and, so far, that's been enough for me to reliably get wifi back after waking from
sleep.