Skip to content

Instantly share code, notes, and snippets.

@rgov
Created October 16, 2022 18:27
Show Gist options
  • Save rgov/4d48bc4f55403cd73ed07cdedf29b08e to your computer and use it in GitHub Desktop.
Save rgov/4d48bc4f55403cd73ed07cdedf29b08e to your computer and use it in GitHub Desktop.
Example of building a custom Ubuntu kernel + metapackages

This repository contains files for compiling a custom Ubuntu kernel with metapackages.

The build script is based on these instructions from the Ubuntu wiki.

Differences from mainline

  • Permits the use of the CTS pin on a serial port for PPS input
  • Excludes the Nvidia DKMS modules

Building with Docker

First, a container image containing build dependencies and a pristine copy of the kernel sources is built.

# Ubuntu 18.04 Bionic
docker build . \
    --tag linux-build:bionic \
    --build-arg RELEASE_CODENAME=bionic \
    --build-arg KERNEL_VERSION=4.15.0-176-generic

# Ubuntu 20.04 Focal
docker build . \
    --tag linux-build:focal \
    --build-arg RELEASE_CODENAME=focal \
    --build-arg KERNEL_VERSION=5.4.0-65-generic

This container image is used to build the kernel:

docker run --rm \
   -v $(pwd):/patches \
   -v $(pwd)/pkgs:/output \
   linux-build:bionic

Note that it can take several hours to build the kernel and drivers.

The resulting .deb files will be placed under pkgs/linux/ and pkgs/linux-meta/ in the current directory.

Patching

The build script applies patches that are prefixed with the Ubuntu version codename, e.g., bionic-01-blah-blah.patch.

Setting the environment variable EDITCONFIG (e.g., docker run -e EDITCONFIG=1 ...) will drop into an interactive shell. A patch can be created with:

(cd / && diff -ru $(readlink -f linux-orig) $(readlink -f linux))

To modify the kernel configuration, use the following command to modify the config amd64/config.flavour.generic.

LANG=C fakeroot debian/rules editconfigs
Adapted from
https://www.spinics.net/lists/linux-serial/msg31949.html
Add a "pps_4wire" file to serial ports in sysfs in case the kernel is
configured with CONFIG_PPS_CLIENT_LDISC. Writing 1 to the file enables
the use of CTS instead of DCD for PPS signal input. This is necessary
in case a serial port is not completely wired.
Though this affects PPS processing the patch is against the serial core
as the source of the serial port PPS event dispatching has to be
modified. Furthermore it should be possible to modify the source of
serial port PPS event dispatching before changing the line discipline.
Signed-off-by: Andreas Steinmetz <[email protected]>
Signed-off-by: Steve Sakoman <[email protected]>
Tested-by: Steve Sakoman <[email protected]>
---
--- a/Documentation/ABI/testing/sysfs-tty 2021-10-05 00:57:15.386317636 +0000
+++ b/Documentation/ABI/testing/sysfs-tty 2021-10-05 00:58:27.316013095 +0000
@@ -154,3 +154,12 @@
device specification. For example, when user sets 7bytes on
16550A, which has 1/4/8/14 bytes trigger, the RX trigger is
automatically changed to 4 bytes.
+
+What: /sys/class/tty/ttyS0/pps_4wire
+Date: September 2018
+Contact: Steve Sakoman <[email protected]>
+Description:
+ Shows/sets "4 wire" mode for the PPS input to the serial driver.
+ For fully implemented serial ports PPS is normally provided
+ on the DCD line. For partial "4 wire" implementations CTS is
+ used instead of DCD.
--- a/drivers/tty/serial/serial_core.c 2021-10-05 00:56:18.066442716 +0000
+++ b/drivers/tty/serial/serial_core.c 2021-10-05 01:02:32.225098247 +0000
@@ -2711,6 +2711,56 @@
return snprintf(buf, PAGE_SIZE, "%d\n", tmp.iomem_reg_shift);
}
+static ssize_t uart_get_attr_pps_4wire(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct tty_port *port = dev_get_drvdata(dev);
+ struct uart_state *state = container_of(port, struct uart_state, port);
+ struct uart_port *uport;
+ int mode = 0;
+
+ mutex_lock(&port->mutex);
+ uport = uart_port_check(state);
+ if (!uport)
+ goto out;
+
+ mode = uport->pps_4wire;
+
+out:
+ mutex_unlock(&port->mutex);
+ return snprintf(buf, PAGE_SIZE, mode ? "yes" : "no");
+}
+
+static ssize_t uart_set_attr_pps_4wire(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct tty_port *port = dev_get_drvdata(dev);
+ struct uart_state *state = container_of(port, struct uart_state, port);
+ struct uart_port *uport;
+ bool mode;
+ int ret;
+
+ if (!count)
+ return -EINVAL;
+
+ ret = kstrtobool(buf, &mode);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&port->mutex);
+ uport = uart_port_check(state);
+ if (!uport)
+ goto out;
+
+ spin_lock_irq(&uport->lock);
+ uport->pps_4wire = mode;
+ spin_unlock_irq(&uport->lock);
+
+out:
+ mutex_unlock(&port->mutex);
+ return count;
+}
+
static DEVICE_ATTR(type, S_IRUSR | S_IRGRP, uart_get_attr_type, NULL);
static DEVICE_ATTR(line, S_IRUSR | S_IRGRP, uart_get_attr_line, NULL);
static DEVICE_ATTR(port, S_IRUSR | S_IRGRP, uart_get_attr_port, NULL);
@@ -2724,6 +2774,7 @@
static DEVICE_ATTR(io_type, S_IRUSR | S_IRGRP, uart_get_attr_io_type, NULL);
static DEVICE_ATTR(iomem_base, S_IRUSR | S_IRGRP, uart_get_attr_iomem_base, NULL);
static DEVICE_ATTR(iomem_reg_shift, S_IRUSR | S_IRGRP, uart_get_attr_iomem_reg_shift, NULL);
+static DEVICE_ATTR(pps_4wire, S_IRUSR | S_IWUSR | S_IRGRP, uart_get_attr_pps_4wire, uart_set_attr_pps_4wire);
static struct attribute *tty_dev_attrs[] = {
&dev_attr_type.attr,
@@ -2739,6 +2790,7 @@
&dev_attr_io_type.attr,
&dev_attr_iomem_base.attr,
&dev_attr_iomem_reg_shift.attr,
+ &dev_attr_pps_4wire.attr,
NULL,
};
@@ -2795,6 +2847,9 @@
goto out;
}
+ /* assert that pps handling is done via DCD as default */
+ uport->pps_4wire = 0;
+
/*
* If this port is a console, then the spinlock is already
* initialised.
@@ -2971,7 +3026,7 @@
lockdep_assert_held_once(&uport->lock);
- if (tty) {
+ if (tty && !uport->pps_4wire) {
ld = tty_ldisc_ref(tty);
if (ld) {
if (ld->ops->dcd_change)
@@ -3000,8 +3055,21 @@
*/
void uart_handle_cts_change(struct uart_port *uport, unsigned int status)
{
+ struct tty_port *port = &uport->state->port;
+ struct tty_struct *tty = port->tty;
+ struct tty_ldisc *ld;
+
lockdep_assert_held_once(&uport->lock);
+ if (tty && uport->pps_4wire) {
+ ld = tty_ldisc_ref(tty);
+ if (ld) {
+ if (ld->ops->dcd_change)
+ ld->ops->dcd_change(tty, status);
+ tty_ldisc_deref(ld);
+ }
+ }
+
uport->icount.cts++;
if (uart_softcts_mode(uport)) {
--- a/include/linux/serial_core.h 2021-10-05 01:10:35.360386349 +0000
+++ b/include/linux/serial_core.h 2021-10-05 01:10:45.073386641 +0000
@@ -248,7 +248,8 @@
struct device *dev; /* parent device */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
- unsigned char unused[2];
+ unsigned char pps_4wire; /* CTS instead of DCD */
+ unsigned char unused;
const char *name; /* port name */
struct attribute_group *attr_group; /* port specific attributes */
const struct attribute_group **tty_groups; /* all attributes (serial core use only) */
#!/bin/bash
set -eu -o pipefail
shopt -s nullglob
# For example: 4.15.0-122-generic
KERNEL_VERSION="${KERNEL_VERSION:-$(uname -r)}"
# For example: bionic
[ -z "$RELEASE_CODENAME" ] && source /etc/lsb-release
# Drop into an interactive shell on error
function on_error {
echo
echo
echo "An error occurred, you are being dropped into an interactive shell:"
exec /bin/bash
}
trap on_error ERR
# Start in the Linux source tree
cd /linux/
# Update the package version
sed -i -E '1 s/\((.*)\)/(\1+lrauv)/' debian.master/changelog
# Disable the module check
# https://wiki.ubuntu.com/KernelTeam/KernelMaintenance#Overriding_module_check_failures
PREV_ABIDIR=$(echo debian.master/abi/*/amd64)
# For example,
# sed -i "/gpio-pca953x/d" "$PREV_ABIDIR"/generic.modules
# Apply patches
for PATCH in "/patches/${RELEASE_CODENAME}-"*.patch; do
patch -p1 < "$PATCH"
done
# If requested, drop into a shell
if [ -n "${EDITCONFIG:-}" ]; then
LINUX_DIR=$(echo ../linux-*/ | sed -e 's,/$,,')
cp -r ../linux ../linux-orig
exec /bin/bash
fi
# On Ubuntu Bionic, the build process tries to download an Nvidia driver package
# that has been pruned from the package repository. Since our system does not
# use any Nvidia components, we just disable it.
sed -i '/do_dkms_nvidia/ s/true/false/' debian.master/rules.d/amd64.mk
# Remove flavors we do not want to build
find debian.master/control.d -type f \
-iname 'vars.*' -not -iname 'vars.generic' \
-delete
# Build
LANG=C fakeroot debian/rules clean
LANG=C fakeroot debian/rules binary-headers binary-generic
# Move built packages
mkdir -p /output/linux/
mv ../*.deb /output/linux/
# Build the linux-meta packages
cd /linux-meta/
# Update the package version
sed -i -E '1 s/\((.*)\)/(\1+lrauv)/' debian/changelog
# Remove flavors we do not want to build
find debian/control.d -type f -not -iname 'generic' -delete
# Modify dependencies to use our unsigned kernel
sed -i '/^Package: linux-image-generic@SUFFIX@$/,/^$/{/^Depends:/ s/linux-image-/linux-image-unsigned-/}' \
debian/control.d/generic
# Build all the packages. This is not documented anywhere!
dpkg-buildpackage
# Move built packages
mkdir -p /output/linux-meta/
mv ../*.deb /output/linux-meta/
ARG RELEASE_CODENAME=bionic
ARG KERNEL_VERSION=4.15.0-176-generic
FROM ubuntu:${RELEASE_CODENAME} AS common_deps
# A pecularity of Docker is that we need to repeat args here
ARG RELEASE_CODENAME
# Add source repositories
RUN echo "deb-src http://archive.ubuntu.com/ubuntu ${RELEASE_CODENAME} main" >> /etc/apt/sources.list \
&& echo "deb-src http://archive.ubuntu.com/ubuntu ${RELEASE_CODENAME}-updates main" >> /etc/apt/sources.list
# Install dependencies before we can download anything
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt update \
&& apt install -y \
dpkg-dev \
&& rm -rf /var/lib/apt/lists/*
FROM common_deps AS downloader
ARG KERNEL_VERSION
# Now download the Linux sources
RUN apt update \
&& apt source linux-generic \
&& apt source "linux-image-unsigned-${KERNEL_VERSION}" \
&& rm -rf /var/lib/apt/lists/* \
&& mv linux-[0-9]*/ linux/ \
&& mv linux-meta-[0-9]*/ linux-meta/
FROM common_deps
ARG RELEASE_CODENAME
ARG KERNEL_VERSION
# Install dependencies
RUN export DEBIAN_FRONTEND=noninteractive \
&& apt update \
&& apt build-dep -y linux-generic \
&& apt build-dep -y "linux-image-${KERNEL_VERSION}" \
&& apt install -y \
bc \
bison \
cpio \
curl \
flex \
dkms \
gawk \
kernel-wedge \
kmod \
libelf-dev \
libssl-dev \
ncurses-dev \
openssl \
&& rm -rf /var/lib/apt/lists/*
# FIXME: What about lz4? Focal only?
# Copy downloaded sources
COPY --from=downloader linux/ linux/
COPY --from=downloader linux-meta/ linux-meta/
# Mark scripts executable
RUN chmod a+x linux/debian/rules \
&& chmod a+x linux/debian/scripts/* \
&& chmod a+x linux/debian/scripts/misc/*
# Install the build script
COPY build.sh /
# Define volume mounts
VOLUME [ "/patches", "/output" ]
# Expose build arguments as environment variables
ENV RELEASE_CODENAME=${RELEASE_CODENAME}
ENV KERNEL_VERSION=${KERNEL_VERSION}
CMD [ "/build.sh" ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment