Skip to content

Instantly share code, notes, and snippets.

@beslovas
Forked from jbuncle/build-patched-kernel.sh
Created February 11, 2021 16:10
Show Gist options
  • Save beslovas/a6545a9f1e90f813a273046295ab7cdb to your computer and use it in GitHub Desktop.
Save beslovas/a6545a9f1e90f813a273046295ab7cdb to your computer and use it in GitHub Desktop.
Compile and install Linux Kernel with patch for Lenovo Legion 5 15ARH05 Touchpad
#! /bin/bash
#
# Lenovo Legion 5 Patch Utility
#
# This script aims to simplify the application of various patches required for Lenovo Legion 5 15ARH05.
# For convenience you can run this script with `bash <(curl https://gist.githubusercontent.com/jbuncle/7dacde983b3c33b3b816b10e2fd2308a/raw/build-patched-kernel.sh)`
#
# References:
# - Original bug and related patch: https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1887190
# - Touchpad patch file: https://www.spinics.net/lists/linux-input/msg69458.html
# - Tutorial for building the linux kernel: https://www.freecodecamp.org/news/building-and-installing-the-latest-linux-kernel-from-source-6d8df5345980/, then adapted based on https://wiki.ubuntu.com/KernelTeam/GitKernelBuild
#
set -e
KERNEL_VERSION_511='5.11-rc6'
PROC=`getconf _NPROCESSORS_ONLN`
install_build_deps() {
sudo apt-get install git fakeroot build-essential ncurses-dev xz-utils libssl-dev bc flex bison libelf-dev dwarves
}
prompt_kernel_version() {
while true; do
echo ""
echo "=============================================================="
echo ""
echo "What version of the kernel do you want to use, '5.8', '5.9' or '${KERNEL_VERSION_511}?'"
echo "5.8 and 5.9 are stable and will be patched with the required changes to allow the touchpad to work."
echo "5.11 is currently a release candidate and therefore not fully stable, however it will not need patching as it contains the necessary changes already."
echo ""
echo "Enter '5.8', '5.9' or '${KERNEL_VERSION_511}':"
read -p "" BUILD_MODE
case $BUILD_MODE in
"5.9" )
KERNEL_VERSION='5.9'
break
;;
"5.8" )
KERNEL_VERSION='5.8'
break;
;;
"${KERNEL_VERSION_511}" )
KERNEL_VERSION=${KERNEL_VERSION_511}
break;
;;
* )
echo "Please enter either 5.11-rc6, 5.9, 5.8."
;;
esac
done
}
#
# Generate Kernel configuration based on current systems existing config.
#
generate_config() {
# Copy in old config
cp /boot/config-`uname -r` .config
echo ""
echo "About to run 'make oldconfig', do you want to accept all new kernel options automatically or do you want to be prompted for each one? [y/n]"
echo " y - (Default) Accept all new options"
echo " n - Prompt for each option"
read -p "" ACCEPT_NEW_CONFIG
case $ACCEPT_NEW_CONFIG in
[Nn]* )
make oldconfig
;;
* )
yes '' | make oldconfig
;;
esac
# Allow adjustments to kernel config
make -j ${PROC} menuconfig
}
#
# Check if we have an existing download of the kernel in the current directory
#
has_kernel() {
if [ "${1}" = "${KERNEL_VERSION_511}" ] ; then
test -f linux-${1}.tar.gz
else
test -f linux-${1}.tar.xz
fi
}
#
# Download kernel into current directory
#
download_kernel() {
echo "Downloading kernel"
if [ "${1}" = "${KERNEL_VERSION_511}" ] ; then
wget https://git.kernel.org/torvalds/t/linux-${1}.tar.gz
else
wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-${1}.tar.xz
fi
}
#
# Extract kernel
#
extract_kernel() {
echo "Extracting kernel"
if [ "${1}" = "${KERNEL_VERSION_511}" ] ; then
tar xf linux-${1}.tar.gz
else
tar xf linux-${1}.tar.xz
fi
}
#
# Apply touchpad kernel patch
#
apply_patch() {
# Apply a suggested patch for Lenovo Legion 5 touchpad
echo "Downloading patch"
wget https://gist.githubusercontent.com/jbuncle/7dacde983b3c33b3b816b10e2fd2308a/raw/touchpad-kernel-workaround.patch
echo "Applying patch"
# TODO: Use downloaded script
patch -p1 -i touchpad-kernel-workaround.patch
}
#
# Fetches kernel using given version, extracts and enters the source directory.
#
get_kernel_source() {
echo "Using version ${1}"
cd /tmp/
test -d kernel-build-${1} || mkdir kernel-build-${1}
cd kernel-build-${1}
# Check for existing download
if [ ! -d linux-${1} ] ; then
# Fetch kernel source
has_kernel ${1} || download_kernel ${1}
# Extract kernel
extract_kernel ${1}
cd linux-${1}
if [ "${1}" != "${KERNEL_VERSION_511}" ] ; then
apply_patch
else
echo "Not applying patches (using version '${KERNEL_VERSION_511}' which should already contain required updates )"
fi
else
echo "Using existing downloaded source"
cd linux-${1}
fi
}
build_and_install_kernel() {
LOCALVERSION=-touchpad-patch
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
prompt_kernel_version
VERSION=${KERNEL_VERSION}
get_kernel_source ${VERSION}
generate_config
# Build kernel deb packages
echo "Running 'make clean'"
make -j ${PROC} clean
echo "Creating linux kernel deb packages with 'make deb-pkg'"
sudo make -j ${PROC} CONFIG_HID=y CONFIG_I2C_HID=y deb-pkg LOCALVERSION=${LOCALVERSION}
cd ..
# Locate the freshly built deb packages
LINUX_IMG_DEB=$(ls linux-image-*_amd64.deb | grep -v "\-dbg_" | head -n 1)
LINUX_IMG_HEADERS=$(ls linux-headers-*_amd64.deb | head -n 1)
# Install the new kernel with deb package manager
sudo dpkg -i ${LINUX_IMG_DEB}
sudo dpkg -i ${LINUX_IMG_HEADERS}
echo ""
echo "=========="
echo "Now update '/etc/default/grub', setting GRUB_CMDLINE_LINUX_DEFAULT to contain 'i2c_hid.polling_mode=1' e.g.:"
echo ' GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i2c_hid.polling_mode=1"'
echo "(Don't forget to run 'sudo update-grub')"
echo 'Then reboot and select the new kernel'
echo "=========="
echo ""
# TODO: Prompt update and prompt reboot
}
build_and_install_module() {
KERNEL_PATH=$(modinfo --filename i2c-hid)
BUILD_SPACE=/tmp/build/i2c-hid
MODULE_DOWNLOAD=https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1887190/+attachment/5422562/+files/i2c-hid_standalone.zip
MODULE_EXP_CHECKSUM=30cec04d640cbfe0f0f3bc8e68478ebf
# Check module is active
lsmod | grep i2c_hid || (echo "Module i2c_hid doesn't seem to be enabled"; exit)
# Download patched module source
if [ ! -d ${BUILD_SPACE} ] ; then
cd /tmp
rm -rf i2c-hid_standalone i2c-hid_standalone.zip
mkdir -p $(dirname ${BUILD_SPACE})
echo "Downloading module source code"
wget https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1887190/+attachment/5422562/+files/i2c-hid_standalone.zip
DOWNLOAD_MD5=$(md5sum i2c-hid_standalone.zip | awk '{ print $1 }')
if [ "${DOWNLOAD_MD5}" != "${MODULE_EXP_CHECKSUM}" ] ; then
echo "Checksum '${DOWNLOAD_MD5}' failed to match '${MODULE_EXP_CHECKSUM}'"
fi
unzip -d ${BUILD_SPACE} i2c-hid_standalone.zip
rm i2c-hid_standalone.zip
fi
cd ${BUILD_SPACE}/i2c-hid_standalone
echo "Building module"
make -j ${PROC}
# Backup old, but don't overwrite
test -f "${KERNEL_PATH}.old" || (echo "Backing up existing module to ${KERNEL_PATH}.old" ; sudo cp "${KERNEL_PATH}" "${KERNEL_PATH}.old")
# Replace module
echo "Replacing module with patched version"
sudo cp ${BUILD_SPACE}/i2c-hid_standalone/i2c-hid.ko ${KERNEL_PATH}
# Remove existing module if enabled
echo "Removing module"
sudo rmmod i2c-hid || true
# Insert module into the kernel Temporarilty set with polling_mode
echo "Re-enabling with polling_mode=1 (temporary)"
sudo insmod ${KERNEL_PATH} polling_mode=1
echo ""
echo "=========="
# For some reason, when I did it this way, I had to use "i2c_hid" instead of "i2c-hid"
echo "To activate the patch permanently update '/etc/default/grub', setting GRUB_CMDLINE_LINUX_DEFAULT to contain 'i2c_hid.polling_mode=1' e.g.:"
echo 'GRUB_CMDLINE_LINUX_DEFAULT="quiet splash i2c_hid.polling_mode=1"'
echo "(Don't forget to run 'sudo update-grub')"
echo "=========="
echo ""
}
brightness_fix() {
LINE='Option \"RegistryDwords\" \"EnableBrightnessControl=1\"'
FILE='/usr/share/X11/xorg.conf.d/10-nvidia.conf'
AFTER='Option \"AllowEmptyInitialConfiguration\"'
echo "Applying brightness fix"
echo "Checking nvidia config"
(grep -q "${LINE}" ${FILE} && echo "Nvidia config already updated") || sudo sed -i "s/${AFTER}/${AFTER}\n ${LINE}/" "${FILE}"
echo "Checking Grub option"
(grep -q "video.use_native_backlight=1" /etc/default/grub && echo "Grub already updated") || (\
echo "Inserting option\n"; \
sudo sed -i "s/GRUB_CMDLINE_LINUX_DEFAULT=\"quiet splash/GRUB_CMDLINE_LINUX_DEFAULT=\"quiet splash video.use_native_backlight=1/" /etc/default/grub && \
echo "Updating grub\n" && \
sudo update-grub && \
echo "You will need to reboot\n" \
)
echo "Done"
}
help() {
echo " m - (Riskier but faster) Build the i2c-hid module alone and insert it into you current kernel (this could break your kernel - ideally make sure you have other kernels available)."
echo " This may not work if kernel module signature verification is enabled"
echo " k - (Safer but slower) Compile and install the whole kernel (alongside your existing kernel) with a patched i2c-hid module"
echo " b - Apply screen brightness fix (issue where screen brightness cannot be adjusted)"
echo ""
echo "Note, to build the latest kernel (${KERNEL_VERSION_511}) you will need to choose 'k'"
echo ""
}
do_prompt() {
echo ""
echo "This utility script is intended for use with Ubuntu 20 on Lenovo Legion 5"
echo ""
echo "What do you want to do?"
echo "Build patched module [m], build patched kernel [k], apply brightness fix [b], more info [h]:"
read -p "" BUILD_MODE
case $BUILD_MODE in
[mM]* )
install_build_deps
build_and_install_module
;;
[kK]* )
install_build_deps
build_and_install_kernel
;;
[bB]* )
brightness_fix
;;
[hH]* | "-h" | * )
help
do_prompt
;;
esac
}
do_prompt
diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c
index dbd04492825d..0bb8075424b6 100644
--- a/drivers/hid/i2c-hid/i2c-hid-core.c
+++ b/drivers/hid/i2c-hid/i2c-hid-core.c
@@ -36,6 +36,8 @@
#include <linux/hid.h>
#include <linux/mutex.h>
#include <linux/acpi.h>
+#include <linux/kthread.h>
+#include <linux/gpio/driver.h>
#include <linux/of.h>
#include <linux/regulator/consumer.h>
@@ -60,6 +62,24 @@
#define I2C_HID_PWR_ON 0x00
#define I2C_HID_PWR_SLEEP 0x01
+/* polling mode */
+#define I2C_POLLING_DISABLED 0
+#define I2C_POLLING_GPIO_PIN 1
+#define POLLING_INTERVAL 10
+
+static u8 polling_mode;
+module_param(polling_mode, byte, 0444);
+MODULE_PARM_DESC(polling_mode, "How to poll - 0 disabled; 1 based on GPIO pin's status");
+
+static unsigned int polling_interval_active_us = 4000;
+module_param(polling_interval_active_us, uint, 0644);
+MODULE_PARM_DESC(polling_interval_active_us,
+ "Poll every {polling_interval_active_us} us when the touchpad is active. Default to 4000 us");
+
+static unsigned int polling_interval_idle_ms = 10;
+module_param(polling_interval_idle_ms, uint, 0644);
+MODULE_PARM_DESC(polling_interval_ms,
+ "Poll every {polling_interval_idle_ms} ms when the touchpad is idle. Default to 10 ms");
/* debug option */
static bool debug;
module_param(debug, bool, 0444);
@@ -158,6 +178,8 @@ struct i2c_hid {
struct i2c_hid_platform_data pdata;
+ struct task_struct *polling_thread;
+
bool irq_wake_enabled;
struct mutex reset_lock;
};
@@ -772,7 +794,9 @@ static int i2c_hid_start(struct hid_device *hid)
i2c_hid_free_buffers(ihid);
ret = i2c_hid_alloc_buffers(ihid, bufsize);
- enable_irq(client->irq);
+
+ if (polling_mode == I2C_POLLING_DISABLED)
+ enable_irq(client->irq);
if (ret)
return ret;
@@ -814,6 +838,86 @@ struct hid_ll_driver i2c_hid_ll_driver = {
};
EXPORT_SYMBOL_GPL(i2c_hid_ll_driver);
+static int get_gpio_pin_state(struct irq_desc *irq_desc)
+{
+ struct gpio_chip *gc = irq_data_get_irq_chip_data(&irq_desc->irq_data);
+
+ return gc->get(gc, irq_desc->irq_data.hwirq);
+}
+
+static bool interrupt_line_active(struct i2c_client *client)
+{
+ unsigned long trigger_type = irq_get_trigger_type(client->irq);
+ struct irq_desc *irq_desc = irq_to_desc(client->irq);
+
+ /*
+ * According to Windows Precsiontion Touchpad's specs
+ * https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-precision-touchpad-device-bus-connectivity,
+ * GPIO Interrupt Assertion Leve could be either ActiveLow or
+ * ActiveHigh.
+ */
+ if (trigger_type & IRQF_TRIGGER_LOW)
+ return !get_gpio_pin_state(irq_desc);
+
+ return get_gpio_pin_state(irq_desc);
+}
+
+static int i2c_hid_polling_thread(void *i2c_hid)
+{
+ struct i2c_hid *ihid = i2c_hid;
+ struct i2c_client *client = ihid->client;
+ unsigned int polling_interval_idle;
+
+ while (1) {
+ /*
+ * re-calculate polling_interval_idle
+ * so the module parameters polling_interval_idle_ms can be
+ * changed dynamically through sysfs as polling_interval_active_us
+ */
+ polling_interval_idle = polling_interval_idle_ms * 1000;
+ if (test_bit(I2C_HID_READ_PENDING, &ihid->flags))
+ usleep_range(50000, 100000);
+
+ if (kthread_should_stop())
+ break;
+
+ while (interrupt_line_active(client)) {
+ i2c_hid_get_input(ihid);
+ usleep_range(polling_interval_active_us,
+ polling_interval_active_us + 100);
+ }
+
+ usleep_range(polling_interval_idle,
+ polling_interval_idle + 1000);
+ }
+
+ do_exit(0);
+ return 0;
+}
+
+static int i2c_hid_init_polling(struct i2c_hid *ihid)
+{
+ struct i2c_client *client = ihid->client;
+
+ if (!irq_get_trigger_type(client->irq)) {
+ dev_warn(&client->dev,
+ "Failed to get GPIO Interrupt Assertion Level, could not enable polling mode for %s",
+ client->name);
+ return -1;
+ }
+
+ ihid->polling_thread = kthread_create(i2c_hid_polling_thread, ihid,
+ "I2C HID polling thread");
+
+ if (ihid->polling_thread) {
+ pr_info("I2C HID polling thread");
+ wake_up_process(ihid->polling_thread);
+ return 0;
+ }
+
+ return -1;
+}
+
static int i2c_hid_init_irq(struct i2c_client *client)
{
struct i2c_hid *ihid = i2c_get_clientdata(client);
@@ -997,6 +1101,15 @@ static void i2c_hid_fwnode_probe(struct i2c_client *client,
pdata->post_power_delay_ms = val;
}
+static void free_irq_or_stop_polling(struct i2c_client *client,
+ struct i2c_hid *ihid)
+{
+ if (polling_mode != I2C_POLLING_DISABLED)
+ kthread_stop(ihid->polling_thread);
+ else
+ free_irq(client->irq, ihid);
+}
+
static int i2c_hid_probe(struct i2c_client *client,
const struct i2c_device_id *dev_id)
{
@@ -1090,7 +1203,11 @@ static int i2c_hid_probe(struct i2c_client *client,
if (ret < 0)
goto err_regulator;
- ret = i2c_hid_init_irq(client);
+ if (polling_mode != I2C_POLLING_DISABLED)
+ ret = i2c_hid_init_polling(ihid);
+ else
+ ret = i2c_hid_init_irq(client);
+
if (ret < 0)
goto err_regulator;
@@ -1129,7 +1246,7 @@ static int i2c_hid_probe(struct i2c_client *client,
hid_destroy_device(hid);
err_irq:
- free_irq(client->irq, ihid);
+ free_irq_or_stop_polling(client, ihid);
err_regulator:
regulator_bulk_disable(ARRAY_SIZE(ihid->pdata.supplies),
@@ -1146,7 +1263,7 @@ static int i2c_hid_remove(struct i2c_client *client)
hid = ihid->hid;
hid_destroy_device(hid);
- free_irq(client->irq, ihid);
+ free_irq_or_stop_polling(client, ihid);
if (ihid->bufsize)
i2c_hid_free_buffers(ihid);
@@ -1162,7 +1279,7 @@ static void i2c_hid_shutdown(struct i2c_client *client)
struct i2c_hid *ihid = i2c_get_clientdata(client);
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
- free_irq(client->irq, ihid);
+ free_irq_or_stop_polling(client, ihid);
}
#ifdef CONFIG_PM_SLEEP
@@ -1183,15 +1300,16 @@ static int i2c_hid_suspend(struct device *dev)
/* Save some power */
i2c_hid_set_power(client, I2C_HID_PWR_SLEEP);
- disable_irq(client->irq);
-
- if (device_may_wakeup(&client->dev)) {
- wake_status = enable_irq_wake(client->irq);
- if (!wake_status)
- ihid->irq_wake_enabled = true;
- else
- hid_warn(hid, "Failed to enable irq wake: %d\n",
- wake_status);
+ if (polling_mode == I2C_POLLING_DISABLED) {
+ disable_irq(client->irq);
+ if (device_may_wakeup(&client->dev)) {
+ wake_status = enable_irq_wake(client->irq);
+ if (!wake_status)
+ ihid->irq_wake_enabled = true;
+ else
+ hid_warn(hid, "Failed to enable irq wake: %d\n",
+ wake_status);
+ }
} else {
regulator_bulk_disable(ARRAY_SIZE(ihid->pdata.supplies),
ihid->pdata.supplies);
@@ -1208,7 +1326,7 @@ static int i2c_hid_resume(struct device *dev)
struct hid_device *hid = ihid->hid;
int wake_status;
- if (!device_may_wakeup(&client->dev)) {
+ if (!device_may_wakeup(&client->dev) || polling_mode != I2C_POLLING_DISABLED) {
ret = regulator_bulk_enable(ARRAY_SIZE(ihid->pdata.supplies),
ihid->pdata.supplies);
if (ret)
@@ -1225,7 +1343,8 @@ static int i2c_hid_resume(struct device *dev)
wake_status);
}
- enable_irq(client->irq);
+ if (polling_mode == I2C_POLLING_DISABLED)
+ enable_irq(client->irq);
/* Instead of resetting device, simply powers the device on. This
* solves "incomplete reports" on Raydium devices 2386:3118 and
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment