Ziva Vatra - home :: Odds and Sods :: FreeBSD on Thinkpad X201

FreeBSD on the Lenovo Thinkpad X201

After many many years of running Linux on my Thinkpad X201 I decided to move over to FreeBSD.

Unlike a Linux install where everything auto-configured and worked out of the box, installing FreeBSD on a laptop in 2024 is very much like installing Linux back in the late 2000's. The basics are there but you need some tweaks to make it actually usable. So I created this page to document what needs to be done, both for my future reference and so that anyone else coming across these issues doesn't have to debug everything from scratch.

Some of these bits of information are also to assist long time Linux users to move to FreeBSD. While they have their similarities they also have their differences, which I will document here in relation to the X201.

Note that I don't cover the basics like installing packages, etc... The assumption is that you are comfortable with Unix operating systems already and for FreeBSD basics I can recommend the FreeBSD Handbook.

The install

As this is a laptop I take outside all the time the chances it gets lost or stolen are higher than a desktop, I went for a ZFS Encrypted zpool.

The install itself was flawless, and after about 20 mins (mostly downloading packages) I had a bootable FreeBSD OS.

Setting up X11

This also proved to be simple. In fact it was so simple all I had to do was install "xinit" and run "startx". For the purposes of being explicit I did generate an xorg.conf file with the "X -configure" and saved it to /etc/X11/xorg.conf, but it is not required.

With that done I had a basic X11 session running. I went a bit further and installed fluxbox (my preferred window manager on small screens), configured my .xinitrc and went on my merry way.

Setting up suspend/resume

The second thing I tried to use was suspend/resume. On FreeBSD you type "acpiconf -s 3" on the command line to suspend the machine to RAM (if you want to suspend to disk, you run "acpiconf -s 4"). This bit worked fine, however when the machine resumed the screen remained off. The machine worked fine and I could SSH into it, but without the screen on I could not actually use it.

After quite a bit of research and a lot of help from the folks over at forums.freebsd.org I discovered that you have to load the Intel Graphics driver in order for this to work. By default FreeBSD has the VESA driver configured which is why my X11 session worked without any special driver install, but for more advanced things like suspend/resume you need the official driver.

Interestingly the FreeBSD community decided that instead of writing their own drivers all over again, they would implement a shim for the Linux DRM interface. This allows you to use Linux graphics drivers in FreeBSD.

To make use of this you have to install the "drm-kmod" package, once that is done you add your driver to the kld_list variable in /etc/rc.conf, as so:

kld_list="i915kms"

You can manually load up the driver using "kldload i915kms". If it all works as intended your console will change its graphics. You will go from the more traditional 80 column large font UNIX console to much smaller font console, akin to the Linux framebuffer console.

Interestingly I did not need to alter the xorg.conf. Its default Driver is "modesetting", which will automatically use the best available graphics driver. All I had to do was "startx" again to get my X session running.

Now suspend/resume worked perfectly.

Setting suspend on lid-close

If you are like me and like your laptop to suspend to RAM when you close the lid, you need to set the sysctl, as so:

root@Io~# sysctl hw.acpi.lid_switch_state=S3
hw.acpi.lid_switch_state: NONE -> S3

To make it permanent you put a line "hw.acpi.lid_switch_state=S3" in your /etc/sysctl.conf file.

Now when you close/open the lid the machine goes to sleep/resume. It is nice that this is configurable in sysctl on FreeBSD because it allows you to dynamically change the setting. So if by default you set it to suspend on lid close, but on one occasion you want to disable this, just change the above back to "NONE" (as root). It will persist until changed again or you reboot, after which whatever you set in /etc/sysctl.conf is set again.

Hibernate

To go into hibernate you need to run "acpiconf -s4". Unfortunately it doesn't currently work on my x201. The machine powers off but when you power on again it is a clean instance, you've lost all your state. This is inconvenient for me as on Linux the x201 would auto-hibernate when battery was below 5%, allowing me to swap batteries and quickly resume my work.

Power and battery

To get information on your battery and its current state, you use "acpiconf -i $batt_number", where $batt_number which battery you want information from (on the x201 you only have one battery, so this is '0', some of the larger thinkpads had an option of two batteries).

#$ acpiconf -i 0
Design capacity:	73260 mWh
Last full capacity:	38820 mWh
Technology:		secondary (rechargeable)
Design voltage:		11100 mV
Capacity (warn):	1941 mWh
Capacity (low):		200 mWh
Low/warn granularity:	1 mWh
Warn/full granularity:	1 mWh
Model number:		08K8193
Serial number:		   15
Type:			LION
OEM info:		JingYi
State:			discharging
Remaining capacity:	94%
Remaining time:		1:58
Present rate:		18569 mW
Present voltage:	11760 mV

As you can see from above my "Design capacity" and "Last full capacity" are very different, my battery only has about 50% of its original capacity which makes sense considering that it is very old at this point. Still even at 50% capacity I get around 2h of use with the backlight at 100% (which is the largest power draw on the laptop).

Backlight control

On Linux the backlight was easily controlled by the fn+HOME/END keys as this was actually handled by the BIOS and not Linux

On FreeBSD however it does not work. It looks like the FreeBSD actually takes control of these keys from the BIOS so they no longer work.

First thing was to find out how to set the backlight in software, so that I can at least control it. I found out that there is an aptly named program called "backlight", which does the trick:

root@Io:~ # backlight
brightness: 29
root@Io:~ # backlight -h
Usage:
	backlight [-q] [-f device]
	backlight [-q] [-f device] -i
	backlight [-f device] value
	backlight [-f device] incr|+ value
	backlight [-f device] decr|- value

Simple enough and does the job. As an added bonus it does not require root. From what I can see FreeBSD exposes the backlight control in the "/dev/backlight" folder, allowing the backlight program to control brightness on the console or X11 as any user.

Binding the brightness keys

While having backlight control again is nice it is purely software controlled. This means I can't make use of the buttons without some configuration. First thing I need to do is enable the buttons so that they are registered by the OS. In order to do this you need to load the "acpi_video" module. A simple "kldload acpi_video" will suffice.

If it worked you would get no errors, and sysctl will now have some acpi_video options, as so:

dev.acpi_video.0.%parent: vgapci0
dev.acpi_video.0.%pnpinfo: 
dev.acpi_video.0.%location: 
dev.acpi_video.0.%driver: acpi_video
dev.acpi_video.0.%desc: ACPI video extension
dev.acpi_video.%parent: 

Now you should have working keys. You can test by running "xev". If all goes as planned, when you hit fnCTRL + KeyUP/Down you should get responses corresponding to the below:

KeyPress event, serial 33, synthetic NO, window 0xc00001,
    root 0x1cf, subw 0x0, time 42811377, (123,94), root:(945,120),
    state 0x0, keycode 233 (keysym 0x1008ff02, XF86MonBrightnessUp), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 33, synthetic NO, window 0xc00001,
    root 0x1cf, subw 0x0, time 42811377, (123,94), root:(945,120),
    state 0x0, keycode 233 (keysym 0x1008ff02, XF86MonBrightnessUp), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyPress event, serial 36, synthetic NO, window 0xc00001,
    root 0x1cf, subw 0x0, time 42811846, (123,94), root:(945,120),
    state 0x0, keycode 232 (keysym 0x1008ff03, XF86MonBrightnessDown), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 36, synthetic NO, window 0xc00001,
    root 0x1cf, subw 0x0, time 42811846, (123,94), root:(945,120),
    state 0x0, keycode 232 (keysym 0x1008ff03, XF86MonBrightnessDown), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

If you prefer to use xbindkeys, you can run "xbindkeys -k" and get the following output:

"(Scheme function)"
    m:0x0 + c:233
    XF86MonBrightnessUp

"(Scheme function)"
    m:0x0 + c:232
    XF86MonBrightnessDown

Whichever tool you use, you can see the key code combinations you need to set. If you use xbindkeys then its easy, just replace "(Scheme function)" lines above with "brightness + 5" and "brightness - 5" respectively (if you want 5 step change of brightness on button press, otherwise change to taste).

As I am using fluxbox on this machine it has its own keybinding server. To make use of that to control brightness, we have to alter the $HOME/.fluxbox/keys file, to add the following lines:

232 :Exec backlight - 5
233 :Exec backlight + 5

Then reload your fluxbox config (no need to restart your session), and test the buttons. In my case it worked, so success! I went with a 5% step each button press. This gives me 20 steps between 0 and 100% which for me is a good balance. You can set any valid value you like, the larger the value the fewer steps you have and the greater the variance between steps.

Other IBM/Lenovo thinkpad features

There is one other module available, called "acpi_ibm". This one exposes more features of the laptop to userspace control. To load it you "kldload acpi_ibm", and once done you get the following new sysctl fields:

dev.acpi_ibm.0.handlerevents: NONE
dev.acpi_ibm.0.mic_led: 0
dev.acpi_ibm.0.fan: 1
dev.acpi_ibm.0.fan_level: 0
dev.acpi_ibm.0.fan_speed: 2592
dev.acpi_ibm.0.wlan: 1
dev.acpi_ibm.0.bluetooth: 1
dev.acpi_ibm.0.thinklight: 1
dev.acpi_ibm.0.mute: 1
dev.acpi_ibm.0.volume: 7
dev.acpi_ibm.0.lcd_brightness: 0
dev.acpi_ibm.0.hotkey: 2486
dev.acpi_ibm.0.eventmask: 134217727
dev.acpi_ibm.0.events: 1
dev.acpi_ibm.0.availmask: 134217727
dev.acpi_ibm.0.initialmask: 2060
dev.acpi_ibm.0.%parent: acpi0
dev.acpi_ibm.0.%pnpinfo: _HID=IBM0068 _UID=0 _CID=none
dev.acpi_ibm.0.%location: handle=\_SB_.PCI0.LPC_.EC__.HKEY
dev.acpi_ibm.0.%driver: acpi_ibm
dev.acpi_ibm.0.%desc: ThinkPad ACPI Extras

Some of these (like dev.acpi_ibm.0.fan_speed) are read only, but can be useful to for monitoring. Others like dev.acpi_ibm.0.fan can be set (in this case, you can disable or enable your fan). These override the bios, so for example if you set dev.acpi_ibm.0.fan=0 your fan will turn off and it won't turn on no matter how hot the machine gets. Beyond 100°C your CPU will just shut off.

This means some of these options can damage your laptop and should only be used if you know what you are doing. In my case I quite like the ability to script the turning on/off of the thinklight using sysctl. I can incorporate it as an indicator in some of my scripts.

Current settings

So with all of the above, this is how my current settings look to run FreeBSD on the X201:

# In /etc/rc.conf:
kld_list="i915kms acpi_video acpi_ibm"
# In /etc/sysctl.conf
# Make the laptop suspend to RAM on lid close (switch to S4 to suspend to disk)
hw.acpi.lid_switch_state=S3

# Make the machine more responsive (as it is a workstation rather than a server)
# by increasing the context switches
# see https://forums.freebsd.org/threads/what-is-sysctl-kern-sched-preempt_thresh.85601/ 
#kern.sched.preempt_thresh=160 
#kern.eventtimer.timer=HPET

Beyond that I did one more thing. I set set the setuid bit on sysctl, as so:

chmod +s /sbin/sysctl

What this does is tell the OS that any user that calls that binary will have the binary run as the executable owner (in this case root). This allows me to issue sysctl commands as a non-root user, without resorting to things like "sudo" or similar. On a shared machine giving all normal users the ability to alter sysctl would be a security risk, however I am the only user of this machine. If any nefarious intruder got as far as accessing my encrypted volume and logging into FreeBSD, then the machine is most likely physically compromised already, so the setuid bit will be of little concern.
The advantage for setting it setuid is that I can run sysctl from my non-root user account, including in scripts, without dealing with privilege escalation.

Sound

So one surprise I found was that sound did not work "out of the box" on FreeBSD. Looking on the forums people mentioned that sound works only through the headphone jack, no luck with the internal speaker. So I decided to investigate. First thing I did was check what sound cards I have:

#$ cat /dev/sndstat
Installed devices:
pcm0:  (play/rec) default
pcm1:  (play/rec)
pcm2:  (play)
No devices installed from user space.

First surprise was that I actually had three built in sound cards. I then used the "beep" utility to test each one out. I discovered that each sound card is for a specific output, details in the comments:

#$ beep -g 100 -d /dev/dsp0.0  # Headphones
#$ beep -g 100 -d /dev/dsp1.0  # Speakers
#$ beep -g 100 -d /dev/dsp2.0  # Dock stereo speakers

I also found that if the headphones are plugged in the internal speaker is muted, in such a case unless you send output to the "headphones" sound card you will hear nothing on either headphones or speakers. In addition the acpi_ibm kernel module gives you the following sound controls:

dev.acpi_ibm.0.mute: 1 # Boolean for mute/unmute
dev.acpi_ibm.0.volume: 14  # Volume range is from 0 to 14

The "mute" is controlled by the physical mute button on the keyboard. It acts only on the speakers. The headphone jack is unaffected. The physical volume buttons have been taken over by the operating system so out of the box they do nothing. They are visible on xev as so:

KeyPress event, serial 36, synthetic NO, window 0x3800001,
    root 0x1cf, subw 0x0, time 19824318, (665,480), root:(667,506),
    state 0x0, keycode 122 (keysym 0x1008ff11, XF86AudioLowerVolume), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 36, synthetic NO, window 0x3800001,
    root 0x1cf, subw 0x0, time 19824446, (665,480), root:(667,506),
    state 0x0, keycode 122 (keysym 0x1008ff11, XF86AudioLowerVolume), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyPress event, serial 36, synthetic NO, window 0x3800001,
    root 0x1cf, subw 0x0, time 19825195, (665,480), root:(667,506),
    state 0x0, keycode 123 (keysym 0x1008ff13, XF86AudioRaiseVolume), same_screen YES,
    XLookupString gives 0 bytes: 
    XmbLookupString gives 0 bytes: 
    XFilterEvent returns: False

KeyRelease event, serial 36, synthetic NO, window 0x3800001,
    root 0x1cf, subw 0x0, time 19825354, (665,480), root:(667,506),
    state 0x0, keycode 123 (keysym 0x1008ff13, XF86AudioRaiseVolume), same_screen YES,
    XLookupString gives 0 bytes: 
    XFilterEvent returns: False

So once again we can bind these to do what we want. To get it to work on the volume in sysctl as before will need some script work. So I wrote this quick perl script:

#!/usr/bin/env perl
$cmd = shift;
if($cmd eq "up") {
    setVol(getVol() + 1);
} elsif ($cmd eq "down") {
    setVol(getVol() - 1);
} else {
    die("Unknown cmd $cmd\n");
}
sub getVol(){
    return `sysctl -n dev.acpi_ibm.0.volume`;
}
sub setVol() {
    if ($_[0] < 0) { return; } # Can't go below 0
    die("Failed to set volume: $!\n") if system(
        "sysctl", "dev.acpi_ibm.0.volume=$_[0]"
    );
}

I named the script "volume", put it in my $PATH and set it executable. Then in my fluxbox/.keys file I put

122 :Exec volume down
123 :Exec volume up

And with that the volume keys do what they should. For the level of complexity it may be easier to see if you can get FreeBSD to not take over these keys, as then I suspect they will operate the volume directly. This however does give you the flexibility to customise what the buttons do.

Page created: Sat Sep 14 13:35:03 2024 ][ Page last modified: Sat Sep 28 14:46:04 2024 ]