Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Purism Librem 5 #698

Merged
merged 5 commits into from
Sep 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ See code for all available configurations.
| [PINE64 Pinebook Pro](pine64/pinebook-pro/) | `<nixos-hardware/pine64/pinebook-pro>` |
| [Purism Librem 13v3](purism/librem/13v3) | `<nixos-hardware/purism/librem/13v3>` |
| [Purism Librem 15v3](purism/librem/13v3) | `<nixos-hardware/purism/librem/15v3>` |
| [Purism Librem 5r4](purism/librem/5r4) | `<nixos-hardware/purism/librem/5r4>` |
| [Raspberry Pi 2](raspberry-pi/2) | `<nixos-hardware/raspberry-pi/2>` |
| [Raspberry Pi 4](raspberry-pi/4) | `<nixos-hardware/raspberry-pi/4>` |
| [Samsung Series 9 NP900X3C](samsung/np900x3c) | `<nixos-hardware/samsung/np900x3c>` |
Expand Down
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
pine64-pinebook-pro = import ./pine64/pinebook-pro;
purism-librem-13v3 = import ./purism/librem/13v3;
purism-librem-15v3 = import ./purism/librem/15v3;
purism-librem-5r4 = import ./purism/librem/5r4;
raspberry-pi-2 = import ./raspberry-pi/2;
raspberry-pi-4 = import ./raspberry-pi/4;
kobol-helios4 = import ./kobol/helios4;
Expand Down
133 changes: 133 additions & 0 deletions purism/librem/5r4/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Purism Librem 5 revision 4

Purism's [Librem 5] is a privacy-oriented Linux-friendly smartphone.

[Librem 5]: https://puri.sm/products/librem-5/

## Installation procedure

> *Note*
>
> TODO: build a uuu-compatible installer.

Until there's a native installer, the easiest way to install NixOS on Librem 5 seems to be using [Jumpdrive].

[Jumpdrive]: https://github.com/dreemurrs-embedded/Jumpdrive

### Jumpdrive

Jumpdrive is a tiny Linux distribution which presents device's internal storage as USB mass storage when you connect it to a PC.
It also provides a shell session over telnet.

Follow the instructions in the repo to boot into Jumpdrive.
Note that `uuu` is part of `nxpmicro-mfgtools` package in nixpkgs.

Now, plug the device into your PC. A new block device representing Librem 5's internal MMC should appear in `/dev`.
Note down this device path.

### U-Boot

> **Note**
>
> While upstream u-boot does support Librem 5, it can only boot using `boot.scr`, for which NixOS has no native support.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if useful but I'm running an u-boot that supports /boot/extlinux/extlinux.conf: https://forums.puri.sm/t/can-someone-with-serial-console-access-try-nixos-kernel-on-librem-5/19121/27

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's why there's a custom u-boot packages in this repository. This note is still here from the previous PR I'm continuing and I haven't cleaned up everything yet.

>
> There's work on extlinux support in Librem 5's U-Boot here: https://source.puri.sm/a-wai/uboot-imx/-/tree/allow-compressed-kernel
>
> This U-Boot version is packaged in the [`u-boot`] directory.


[`u-boot`]: ./u-boot

Provided you have a way to build Nix derivations for `aarch64-linux` (like a remote builder, [binfmt emulation], or you're building it on the phone itself), just run `nix-build u-boot/build.nix`.

[binfmt emulation]: https://search.nixos.org/options?channel=22.11&show=boot.binfmt.emulatedSystems&from=0&size=50&sort=relevance&type=packages&query=binfmt

> **Warning**
>
> Even though I've tested this myself, I can't guarantee that this will not render your device unbootable.
> Proceed with caution.
>
> If it does not work, your best bet is to follow the advice here, which will flash U-Boot build by upstream: https://forums.puri.sm/t/can-someone-with-serial-console-access-try-nixos-kernel-on-librem-5/19121/27

To flash the device, run

```console
$ sudo u-boot-install-librem5 <path to librem 5's MMC>
```

At this point, if you have an OS installed on your Librem 5, it's best to reboot into it to check that the U-Boot was flashed correctly.
If that's the case, reboot back into Jumpdrive.

### Partitioning

Now, from your host system, partition the MMC.

> **Warning**
>
> Doing this wipes all data off the phone

I went with 1 bootable `ext2` partition for `/boot`, and one `ext4` partition for `/`.
It ended up looking like this (your device names will be different):

```console
$ sudo fdisk -l /dev/mmcblk0
Disk /dev/mmcblk0: 29.12 GiB, 31268536320 bytes, 61071360 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: dos
Disk identifier: 0xcec26c32

Device Boot Start End Sectors Size Id Type
/dev/mmcblk0p1 * 4096 499711 495616 242M 83 Linux
/dev/mmcblk0p2 499712 61071359 60571648 28.9G 83 Linux
```

Note 2MiB of free space before the first partition.
This is where U-Boot lives.

Mount the partitions on your host system, e.g. to `/mnt` and `/mnt/boot`.
Remember that `/mnt` is the second partition, and `/mnt/boot` is the first.

### Installation

Now, write your NixOS config.
Use `/dev/mmcblk0p1` as `fileSystems."/boot"` and `/dev/mmcblk0p2` as `fileSystems."/"`.
Don't forget to import the [module from this directory](./default.nix).
If you plan to use the device as a smartphone, you have a choice of two "desktop" (?) environments packaged in nixpkgs: [phosh] and [Plasma Mobile].

[phosh]: https://search.nixos.org/options?channel=22.11&show=services.xserver.desktopManager.phosh.enable&from=0&size=50&sort=relevance&type=packages&query=phosh
[Plasma Mobile]: https://search.nixos.org/options?channel=22.11&show=services.xserver.desktopManager.plasma5.mobile.enable&from=0&size=50&sort=relevance&type=packages

Build the configuration (`nix build .#nixosConfigurations.<hostname>.config.system.build.toplevel` if you're using flakes).

Running `nixos-install --system ./result --root /mnt` will copy the system to the MMC.
Unless you're running on an aarch64 system, it will fail to activate or install the bootloader, however.
You must do this manually.
Get a shell on Jumpdrive, mount partitions there, and activate the system:

```console
$ nc 172.16.42.1 23
# mkdir /mnt
# mount /dev/mmcblk0p2 /mnt
# mkdir -p /mnt/boot
# mount /dev/mmcblk0p1 /mnt/boot
# chroot /mnt /nix/var/nix/profiles/system/activate
# chroot /mnt /nix/var/nix/profiles/system/bin/switch-to-configuration boot
```

Provided the last command succeeds, you now should have a bootable device.

Unmount:

```console
# sync
# umount /mnt/boot
# umount -l /mnt
# echo u > /proc/sysrq-trigger
# echo s > /proc/sysrq-trigger
```

And shut the phone down by holding the power key.

Start it up and you should be booting straight into your NixOS installation.
18 changes: 18 additions & 0 deletions purism/librem/5r4/audio.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{ config, lib, pkgs, ... }:
{
config = lib.mkIf config.hardware.librem5.audio {
assertions = [{
assertion = config.hardware.pulseaudio.enable;
message = "Call audio on Librem5 requires pulse audio to be enabled through `hardware.pulseaudio.enable`.";
}];
hardware.pulseaudio = {
enable = true;
# this is required to correctly configure the modem as PA source/sink
extraConfig = ''
.include ${pkgs.librem5-base}/etc/pulse/librem5.pa
'';
};

services.dbus.packages = [ pkgs.callaudiod ];
};
}
68 changes: 68 additions & 0 deletions purism/librem/5r4/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{ config, pkgs, lib, ... }:
let cfg = config.hardware.librem5;
in {
options = {
hardware.librem5 = {
wifiCard = lib.mkOption {
type = lib.types.enum [ "redpine" "sparklan" "none" ];
description = lib.mdDoc ''
Which wi-fi card is installed in your phone.

Phones shipped before January 2023 have redpine, newer phones have sparklan.
'';
default = "redpine";
};
customInitrdModules = lib.mkEnableOption (lib.mdDoc "use of custom kernel modules in the initrd.");
installUdevPackages = lib.mkEnableOption (lib.mdDoc "installation of udev packages from librem5-base.");
lockdownFix = lib.mkEnableOption (lib.mdDoc "fix for orientation and proximity sensors not working after lockdown.");
audio = lib.mkOption {
description = lib.mdDoc ''
Whether to enable and configure PulseAudio for the Librem5 modem.

This is required for audio during calls to work at all.
'';
type = lib.types.bool;
default = true;
example = false;
};
};
};

imports = [
./audio.nix
./initrd.nix
./wifi.nix
./lockdown-fix.nix
];

config = {
hardware.librem5 = {
customInitrdModules = lib.mkDefault true;
installUdevPackages = lib.mkDefault true;
lockdownFix = lib.mkDefault true;
};

nixpkgs.overlays = [
(import ./kernel)
(final: prev: {
ubootLibrem5 = final.callPackage ./u-boot { };

librem5-base = final.callPackage ./librem5-base { };
})
];

boot = {
kernelParams = [ "rootwait" ];

loader = {
generic-extlinux-compatible.enable = lib.mkDefault true;
grub.enable = false;
};

kernelPackages = lib.mkDefault pkgs.linuxPackages_librem5;
};

services.udev.packages = lib.mkIf cfg.installUdevPackages [ pkgs.librem5-base ];

};
}
64 changes: 64 additions & 0 deletions purism/librem/5r4/initrd.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{ config, pkgs, lib, ... }:
lib.mkIf config.hardware.librem5.customInitrdModules {
boot.initrd = {
kernelModules = [
"bq25890_charger"
"dwc3"
"imx_dcss"
"imx_sdma"
"mtdblock"
"ofpart"
"phy_fsl_imx8mq_usb"
"snvs_pwrkey"
"spi_nor"
"tps6598x"
"xhci_hcd"
"usbcore"
"usb_storage"
"uas"
"xhci_plat_hcd"
];
# Not all default modules (e.g. SATA ones) are present in Librem 5 kernel fork
includeDefaultModules = false;
availableKernelModules = [
"ahci"

"sd_mod"
"sr_mod"

"mmc_block"

"uhci_hcd"
"ehci_hcd"
"ehci_pci"
"ohci_hcd"
"ohci_pci"
"xhci_pci"
"usbhid"
"hid_generic"
"hid_lenovo"
"hid_apple"
"hid_roccat"
"hid_logitech_hidpp"
"hid_logitech_dj"
"hid_microsoft"
"hid_cherry"

"bq25890_charger"
"dwc3"
"imx_dcss"
"imx_sdma"
"mtdblock"
"ofpart"
"phy_fsl_imx8mq_usb"
"snvs_pwrkey"
"spi_nor"
"tps6598x"
"xhci_hcd"
"usbcore"
"usb_storage"
"uas"
"xhci_plat_hcd"
];
};
}
25 changes: 25 additions & 0 deletions purism/librem/5r4/kernel/6.4.5.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{ lib
, buildLinux
, fetchFromGitLab
, ...
} @ args:
buildLinux (args
// rec {
defconfig = "librem5_defconfig";
version = "6.4.5-librem5";
modDirVersion = version;
src = fetchFromGitLab {
domain = "source.puri.sm";
owner = "Librem5";
repo = "linux";
rev = "pureos/6.4.5pureos1";
hash = "sha256-xg/qZ3Lig8oAAa3I+yn4tKPbftBy9Y6fnk8IvB+rm4E=";
};
kernelPatches = [ ];
structuredExtraConfig = with lib.kernel; {
# buildLinux overrides this and defaults to 32, so go back to the value defined librem5_defconfig
# this is required for millipixels to take photos, otherwise the VIDIOC_REQ_BUFS ioctl returns ENOMEM
CMA_SIZE_MBYTES = lib.mkForce (freeform "320");
};
}
// args.argsOverride or { })
4 changes: 4 additions & 0 deletions purism/librem/5r4/kernel/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
final: prev: {
linuxPackages_librem5_6_4_5 = final.linuxPackagesFor (final.callPackage ./6.4.5.nix { });
linuxPackages_librem5 = final.linuxPackages_librem5_6_4_5;
}
48 changes: 48 additions & 0 deletions purism/librem/5r4/librem5-base/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{ stdenv, fetchFromGitLab, shellcheck, kmod, lib }:
stdenv.mkDerivation {
pname = "librem5-base";
version = "unstable";
src = fetchFromGitLab {
domain = "source.puri.sm";
owner = "Librem5";
repo = "librem5-base";
rev = "96b0f920cde9157332b0c16ba1135ee60a3f3259";
hash = "sha256-nR42dk3g0/IkVFygZ7K1SZ2KoQJeDzuMOumKdOOQS5k=";
};

buildPhase = ":";

checkInputs = [ shellcheck ];
doCheck = true;
checkPhase = "make";

installPhase = ''
mkdir -p "$out/bin" "$out/lib/udev/rules.d"
cp -v default/lockdown-support/lockdown-support.sh "$out/bin"
chmod +x "$out/bin/lockdown-support.sh"
cp -v default/gpsd/99-gnss.rules "$out/lib/udev/rules.d"

pushd debian
for rule in librem5-base-defaults.*.udev; do
cp -v "$rule" "$out/lib/udev/rules.d/''${rule#*.}.rules"
done
popd

mkdir -p "$out/etc/pulse"
cp -v "default/audio/pulse/librem5.pa" "$out/etc/pulse/librem5.pa"
substituteInPlace "$out/etc/pulse/librem5.pa" \
--replace ".include /etc/pulse/default.pa" ""
'';

postFixup = ''
sed -i \
-e "s@/usr/sbin/lockdown-support.sh@$out/bin/lockdown-support.sh@g" \
-e "s@/usr/sbin/modprobe@${kmod}/bin/modprobe@g" \
-e "s@/usr/sbin/rmmod@${kmod}/bin/rmmod@g" \
"$out"/lib/udev/rules.d/*.udev.rules
'';

# https://source.puri.sm/Librem5/librem5-base/-/issues/68
# President@Purism promised it's under a free license: https://matrix.to/#/%23community-librem-5%3Atalk.puri.sm/%24hNCtZr7Escmr56uz1eEiaHpakteEXig7b5G8t2W6tWs
meta.license = lib.licenses.free;
}
Loading