A push-based event delivery mechanism for Android that replaces epoll-based input handling, designed to reduce latency, syscall overhead, and power consumption.
Support this project:
đź’° Crypto Donations
Bitcoin (BTC)
3QVD3H1ryqyxhuf8hNTTuBXSbczNuAKaM8
Ethereum (ETH)
0xaFE28A1Dd57660610Ef46C05EfAA363356e98DC7
Solana (SOL)
6uWx4wuHERBpNxyWjeQKrMLBVte91aBzkHaJb8rhw4rn
Monero (XMR)
8C5aCs7Api3WE67GMw54AhQKnJsCg6CVffCuPxUcaKoiMrnaicyvDch8M2CXTm1DJqhpHKxtLvum9Thw4yHn8zeu7sj8qmC
- What is ESM?
- Why ESM? (ESM vs epoll)
- Performance
- Quick Start
- Detailed Build Instructions
- Flashing to Device
- Verifying ESM is Working
- Technical Architecture
- Modified Components
- Troubleshooting
- Known Limitations
- License
ESM (Event Stream Model) is a kernel-level innovation that fundamentally changes how Android handles input events. Instead of the traditional epoll model where processes wake on timeout to check for events, the kernel actively pushes events directly to waiting processes.
Android's InputFlinger traditionally uses epoll to monitor input device file descriptors (/dev/input/event*). This approach requires:
1. Process calls epoll_wait() with a timeout
2. Process blocks until event arrives OR timeout expires
3. Timeout expires frequently, waking process to check for events
4. If events ready, process calls read() to get the event data
5. Process handles event
6. Repeat from step 1
Problems with this approach:
- Frequent timeout wakeups: Process wakes up repeatedly even when no events are ready
- Extra syscall overhead: Separate
read()required after eachepoll_wait()notification - Context switch overhead: Frequent transitions between kernel and user space
- Poor event batching: Each file descriptor handled separately
- Higher CPU usage: More wakeups and syscalls = more CPU cycles wasted
ESM provides a new system call interface that delivers events directly to userspace:
1. Process calls esm_wait() with a buffer - "Wake me when events arrive"
2. Process blocks in TASK_EV_WAIT state
3. Kernel pushes events directly to process's queue as they arrive
4. Kernel wakes process with events already in the buffer
5. Process handles events (no additional read() needed!)
6. Repeat from step 1
Benefits:
- Lower latency: Events delivered directly, no extra
read()syscall - Better batching: Multiple events from multiple devices delivered in single wakeup
- Reduced CPU: Fewer context switches and syscalls
- Simpler code: Single interface for registration and waiting
- Lower power: Fewer wakeups means better battery life
epoll approach (traditional):
// Setup
int epfd = epoll_create1(EPOLL_CLOEXEC);
struct epoll_event ev = {.events = EPOLLIN, .data.fd = fd};
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
// Wait for events
struct epoll_event events[64];
int nfds = epoll_wait(epfd, events, 64, timeout); // Syscall #1
// Read each event separately
for (int i = 0; i < nfds; i++) {
struct input_event iev;
read(events[i].data.fd, &iev, sizeof(iev)); // Syscall #2, #3, #4...
// Process event...
}ESM approach (push-based):
// Setup - register device once
esm_register(fd, (1 << EV_KEY) | (1 << EV_ABS));
// Wait AND receive events in one call
struct esm_event events[64];
int n = esm_wait(events, 64, timeout); // Single syscall!
// Events already delivered - no read() needed
for (int i = 0; i < n; i++) {
// Process events[i].event directly
}| Metric | epoll | ESM |
|---|---|---|
| Syscalls per event batch | epoll_wait + N Ă— read() | Single esm_wait() |
| Context switches per wakeup | 2 (notify + read) | 1 (events in buffer) |
| Event batching | Per-fd notification | Cross-device batching |
- Push vs Pull Semantics: The kernel knows when events happen; pushing is inherently more efficient than polling
- Better Event Coalescing: Kernel batches events from multiple sources before waking the process
- Reduced Scheduler Pressure: Fewer wakeups = better power management
- Unified Interface: Single mechanism for all input events across all devices
ESM's push-based architecture is designed to improve performance in the following areas:
| Metric | Expected Improvement | Rationale |
|---|---|---|
| Input latency | Lower | Direct event push eliminates extra read() syscall |
| Syscall count | Significantly fewer | esm_wait() combines notification + data delivery |
| CPU usage | Lower | Fewer context switches and syscalls |
| Power consumption | Lower | Fewer wakeups enables deeper CPU sleep states |
A comprehensive testing framework has been developed to empirically measure these improvements.
- Ubuntu 18.04+ (20.04/22.04 recommended)
- 16GB+ RAM (32GB recommended)
- 250GB+ free disk space
- Google Pixel 5 with unlocked bootloader
# 1. Initialize and sync (2-6 hours for download)
mkdir ~/android/esm && cd ~/android/esm
repo init -u https://github.com/esm-android/esm-manifest.git
repo sync -c -j8
# 2. Download proprietary binaries
wget https://dl.google.com/dl/android/aosp/google_devices-redfin-sp1a.210812.016.a1-fe9bc5fb.tgz
wget https://dl.google.com/dl/android/aosp/qcom-redfin-sp1a.210812.016.a1-28a4cf6d.tgz
tar -xzf google_devices-redfin-*.tgz && tar -xzf qcom-redfin-*.tgz
./extract-google_devices-redfin.sh # Type "I ACCEPT"
./extract-qcom-redfin.sh # Type "I ACCEPT"
# 3. Build (1-4 hours)
source build/envsetup.sh && lunch aosp_redfin-userdebug
export PRODUCT_KERNEL_VERSION=6.1
./build.sh -j$(nproc)
# 4. Flash
adb reboot bootloader
fastboot flashall -wThat's it! No patches to apply - ESM is already integrated.
sudo apt-get update
sudo apt-get install -y \
git-core gnupg flex bison build-essential zip curl zlib1g-dev \
gcc-multilib g++-multilib libc6-dev-i386 lib32ncurses5-dev \
x11proto-core-dev libx11-dev lib32z1-dev libgl1-mesa-dev \
libxml2-utils xsltproc unzip fontconfig python3 python-is-python3 \
bc cpio rsync libssl-dev openjdk-11-jdk ccachegit config --global user.name "Your Name"
git config --global user.email "your.email@example.com"mkdir -p ~/bin
curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+x ~/bin/repo
export PATH=~/bin:$PATH
echo 'export PATH=~/bin:$PATH' >> ~/.bashrcexport USE_CCACHE=1
export CCACHE_DIR=$HOME/.ccache
ccache -M 50G
echo 'export USE_CCACHE=1' >> ~/.bashrc
echo 'export CCACHE_DIR=$HOME/.ccache' >> ~/.bashrcmkdir -p ~/android/esm
cd ~/android/esm
# Initialize with ESM manifest (includes all ESM changes!)
repo init -u https://github.com/esm-android/esm-manifest.git
# Download source (~100 GB, 2-6 hours depending on connection)
repo sync -c -j8 --force-sync --no-clone-bundleTip: If download fails, just run the same command again to resume.
Google Pixel devices require proprietary binaries for full functionality (GPU drivers, camera, etc.):
cd ~/android/esm
# Download driver binaries for Pixel 5
wget https://dl.google.com/dl/android/aosp/google_devices-redfin-sp1a.210812.016.a1-fe9bc5fb.tgz
wget https://dl.google.com/dl/android/aosp/qcom-redfin-sp1a.210812.016.a1-28a4cf6d.tgz
# Extract
tar -xzf google_devices-redfin-sp1a.210812.016.a1-fe9bc5fb.tgz
tar -xzf qcom-redfin-sp1a.210812.016.a1-28a4cf6d.tgz
# Run extraction scripts (read licenses, type "I ACCEPT" for each)
./extract-google_devices-redfin.sh
./extract-qcom-redfin.shcd ~/android/esm
# Set up build environment
source build/envsetup.sh
# Select build target (Pixel 5, userdebug variant)
lunch aosp_redfin-userdebug
# Set kernel version and build everything including kernel (1-4 hours)
export PRODUCT_KERNEL_VERSION=6.1
./build.sh -j$(nproc)When successful, you'll see:
#### build completed successfully (01:23:45 (hh:mm:ss)) ####
WARNING: This will factory reset your device!
- On device: Settings > About Phone > Tap "Build Number" 7 times
- On device: Settings > System > Developer Options > Enable "OEM unlocking"
- On device: Settings > System > Developer Options > Enable "USB debugging"
adb reboot bootloader
fastboot flashing unlock
# Use volume keys to select "Unlock", press power to confirmcd ~/android/esm
# Reboot to bootloader
adb reboot bootloader
# Flash all partitions (-w wipes userdata for clean install)
fastboot flashall -w
# Device will reboot automaticallyFirst boot takes 2-5 minutes. ESM is now active!
adb shell
# Verify ESM symbols exist in kernel
cat /proc/kallsyms | grep esmYou should see:
ffffffc010xxxxx T esm_register
ffffffc010xxxxx T esm_wait
ffffffc010xxxxx T esm_ctl
ffffffc010xxxxx T is_in_esm_wait
ffffffc010xxxxx T esm_push_event
adb shell dmesg | grep -i esmadb logcat | grep -i esm
# Touch the screen - you should see ESM log messages+------------------------------------------------------------------+
| USERSPACE |
+------------------------------------------------------------------+
| InputFlinger (system_server) |
| +------------------+ |
| | EventHub | |
| | - esm_register() to register input devices |
| | - esm_wait() to receive batched events |
| +------------------+ |
+------------------------------------------------------------------+
| SYSCALL INTERFACE |
| esm_register(fd, event_mask) - Register device for events |
| esm_wait(events, count, timeout) - Wait and receive events |
| esm_ctl(cmd, arg) - Control ESM behavior |
| is_in_esm_wait(pid) - Check if process is waiting |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| KERNEL SPACE |
+------------------------------------------------------------------+
| ESM Core (kernel/esm.c) |
| +------------------------------------------------+ |
| | Per-Process ESM Context | |
| | - Registered device list (hash table) | |
| | - Per-device event queues (kfifo, 128 events) | |
| | - Wait queue for process blocking | |
| +------------------------------------------------+ |
| |
| Input Drivers (evdev) |
| +------------------+ |
| | Touch, Keyboard, | ---> esm_push_event() ---> ESM Core |
| | Buttons, etc. | |
| +------------------+ |
+------------------------------------------------------------------+
- Registration: InputFlinger opens
/dev/input/event*and callsesm_register(fd, event_mask) - Waiting: InputFlinger calls
esm_wait(), blocking inTASK_EV_WAITstate - Event Generation: Hardware generates input, driver calls
esm_push_event() - Distribution: ESM core finds registered contexts by inode, queues event
- Delivery: ESM wakes process, events already in buffer - no
read()needed
- Inode-based matching: Events matched by inode (not fd), so fork() and multiple opens work correctly
- Per-device queues: 128-event kfifo per registered device prevents cross-device blocking
- TASK_EV_WAIT state: New task state lets watchdog distinguish ESM wait from hangs
- Batch delivery: Multiple events from multiple devices returned in single
esm_wait()call
ESM requires changes across the Android stack:
- kernel/esm.c - Core ESM implementation (~1000 lines)
- drivers/input/evdev.c - Event push integration
- include/linux/sched.h - TASK_EV_WAIT state
- arch/arm64 syscall tables - Four new syscalls
- libc/SYSCALLS.TXT - ESM syscall definitions
- libc/include/sys/esm.h - Userspace API header
- services/inputflinger/reader/EventHub.cpp - ESM integration replacing epoll
- Watchdog.java - ESM-aware ANR detection
- Java syscall constants
- Build system integration
- Device configuration
Bionic syscall headers not synced properly.
repo sync bionic
export PRODUCT_KERNEL_VERSION=6.1
./build.sh -j$(nproc)Wrong kernel was built/flashed.
# Rebuild kernel and boot image
export PRODUCT_KERNEL_VERSION=6.1
./build.sh -j$(nproc)
adb reboot bootloader
fastboot flash boot out/target/product/redfin/boot.img
fastboot rebootFlash stock factory image to recover:
# Download from https://developers.google.com/android/images
# Extract and run:
./flash-all.shCheck if ESM is receiving events:
adb shell dmesg | grep -i esm
adb logcat | grep -i inputflingerIf no ESM messages, kernel may have fallen back to epoll (check for epoll_wait in strace).
- Input events only - ESM currently handles input events only, not sockets/files
- ARM64 only - Syscall numbers defined for ARM64 architecture
- Fixed queue size - 128 events per device; events dropped if queue full
- Single registration per FD - Re-registering overwrites previous registration
- Android 12 only - Would need porting for other Android versions
This project contains modifications to the Android Open Source Project (AOSP) and Linux kernel.
- AOSP components: Apache License 2.0
- Linux kernel components: GPL v2
- Documentation: Apache License 2.0
This manifest pulls from the following ESM-modified repositories:
| Repository | Description |
|---|---|
| esm-manifest | This manifest |
| bionic | Bionic with ESM syscalls |
| build | Build system integration |
| device-redfin | Pixel 5 device config |
| frameworks-base | Framework with ESM watchdog |
| frameworks-native | InputFlinger ESM integration |
| libcore | Java ESM constants |
| kernel | Kernel with ESM implementation |
All other AOSP components are pulled from the official Google repositories at android-12.0.0_r3.
Target: Android 12 (android-12.0.0_r3) | Device: Google Pixel 5 (redfin) | Kernel: 4.19 (redbull)