Introduction
In the Yocto mindset, everything gets built from source code. Yet in many cases you need minor modifications to the code, often called patches.
Yocto offers some streamlined and powerful workflows to help you in creating custom patches to existing packages, including the Linux kernel itself.
In this tutorial, we will declare an AT24-style EEPROM connected via I2C bus to a BeagleBone Black in a kernel device tree as an example.
Version notes
This tutorial uses scarthgap as the primary target, which is the current LTS release by the Yocto Project. You can find more infomation on releases here. Supported releases for following the tutorial are:
Yocto Project | Tutorial applies | Maintenance |
---|---|---|
walnascar (5.0) | ![]() |
![]() |
styhead (5.0) | ![]() |
![]() |
scarthgap (5.0) | ![]() |
![]() |
nanbield (4.3) | ![]() |
![]() |
mickledore (4.2) | ![]() |
![]() |
langdale (4.1) | ![]() |
![]() |
kirkstone (4.0) | ![]() |
![]() |
Please note: a failure in the “tutorial applies” column indicates that the instructions do not work without modification. Depending on the combination of release and host Linux distribution, installing other python versions or dependencies might provide a functional state.
Step 0: prepare a meta layer
We will need a meta layer to place our resulting recipe and files in. The rest of this tutorial assumes such a layer is prepared and added to the build already. We will use the name meta-dawg
, and presume that the layer is present at the directory one level up from the build
directory.
Example layout, top level:
.
├── build
├── meta-dawg
└── poky
Also, we expect the shell to be initialized for the build, and the current working directory to be build
.
If this does not reflect your setup, please change the paths and name accordingly.
Step 1: modify the Linux kernel
Use devtool
to prepare the Linux kernel sources matching your build:
devtool modify virtual/kernel
This creates the build/workspace
directory and its substructure:
workspace/
├── appends
│ └── linux-yocto_6.6.bbappend
├── conf
│ └── layer.conf
├── README
└── sources
└── linux-yocto
Note, the kernel version is 6.6 at the time of this writing but will change over time. Please adjust accordingly for your build.
Looking at the tree, we can see the workspace/sources/linux-yocto
path. This holds a full kernel checkout matching the patch and configuration setup of your build setup. So we can start modifying now.
Our board is the Beaglebone Black, and we want to add an I2C-connected AT24-type EEPROM. We selected I2C bus 2, as that one is not occupied by default, and easily accessible through the headers. In order to access it from Linux properly as an exported file, we therefore need to add it to the device tree.
The device tree files in the kernel source tree for this one are:
arch/arm/boot/dts/ti/omap/am335x-boneblack.dts
# which includes
arch/arm/boot/dts/ti/omap/am335x-boneblack-common.dtsi
# which includes
arch/arm/boot/dts/ti/omap/am335x-bone-common.dtsi
So looking at am335x-bone-common.dtsi
, we can find the section declaring I2C-2:
&i2c2 {
pinctrl-names = "default";
pinctrl-0 = <&i2c2_pins>;
status = "okay";
clock-frequency = <100000>;
...
}
The EEPROM binding which we want to add is documented in at24.yaml
:
i2c {
#address-cells = <1>;
#size-cells = <0>;
eeprom@52 {
compatible = "microchip,24c32", "atmel,24c32";
reg = <0x52>;
pagesize = <32>;
wp-gpios = <&gpio1 3 0>;
num-addresses = <8>;
};
};
Thats already pretty close. Just a few tweaks to fit this example to our hardware setup:
- our variant is the 24C64, not the 24C32.
- the bus address - called
reg
here - is 0x50 as we tied all configurable pins to GND, and only one address is used. - we do not need a write protection pin assigned, so the
wp-gpios
directive is not required.
So we add at the bottom of arch/arm/boot/dts/ti/omap/am335x-boneblack.dts
:
&i2c2 {
eeprom: eeprom@50 {
compatible = "atmel,24c64";
reg = <0x50>;
pagesize = <32>;
};
};
We can try the kernel build either directly by
bitbake virtual/kernel
or just rebuild the image preferred image. After flashing, we can check if the device gets properly set up by looking at the sysfs:
ls /sys/bus/i2c/devices
This lists all devices on I2C-buses that the kernel knows about, and 2-0050
, for bus 2, address 0x50, is there. So our change is functional and correct. Feel free to poke the device directory a bit to learn more about device handling in Linux.
Step 2: create a patch and extract it into layer
To create a patch file which we can properly manage later, the first step is to commit our change to the working copy. This is very straightforward the canonical git workflow.
# change into the kernel source directory, if you are not there already
cd workspace/sources/linux-yocto
# add to the staging area
git add arch/arm/boot/dts/ti/omap/am335x-boneblack.dts
# commit
git commit -m "add at24 eeprom at i2c2-50"
Note that this obviously does not add a proper commit message, signed-off-by statement or similar metadata for the sake of brevity.
Moving a patch from the source state in the workspace back to a layer is a core functionality of devtool
.
Move out of the source directory again into the build directory, tell devtool
that you are done with the changes and instruct it to put the resulting recipe and patches into meta-dawg:
devtool finish linux-yocto meta-dawg
Look at meta-dawg
, and you will find a .bbappend
recipe and patch in the recipes-kernel/linux
directory. For the sake of explanation, the recipe looks like this:
FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
SRC_URI += "file://0001-add-at24-eeprom-at-i2c2-50.patch"
What does this mean? Its very simple, after all.
FILESEXTRAPATHS:prepend
tells bitbake to search a given path first, in our case thelinux-yocto
directory next to the recipeSRC_URI +=
adds our resulting patch file to the sources, which “automagically” works correctly asbitbake
knows how to handle patch files in relation to the packages source.
Conclusion
That’s it! Build and run your new kernel and enjoy your customization!
And the best part about it? This will not just work for the Linux kernel, but virtually every source code based package that Yocto knows about.
We’ve chosen a relatively simple patch here which does not provoke conflicts, which will actually reflect reality in most embedded use cases: you will often have minor changes to a given package. But if they are large enough to actually be problematic, you should consider upstreaming, or worse, forking, instead of carrying a patchset out of band.
More in depth documentation can be found in the Yocto Project Linux Kernel Development Manual
If this tutorial was useful to you, please press like, or leave a thank you note to the contributor who put valuable time into this and made it available to you. It will be much appreciated!