This is Blog 1 of a 3-part series on Static Analysis Tools for Linux Device Driver Development.
Blog 1 (This): Coccinelle — Semantic Patching & Code Transformation
Blog 2: Sparse — Type System Guardian
Blog 3: Smatch — Deep Path-Sensitive Analysis
Each blog is a standalone 101 guide you can follow in sequence or jump to whichever tool you need.
Picture this: You’ve spent hours writing a new device driver for Linux, compiled it successfully, and deployed it to production. Everything seems fine until the system crashes mysteriously, and you discover a null pointer dereference that could have been caught before the code ever ran. This is where static analysis tools become invaluable guardians of code quality.
For Linux device drivers, static analysis is particularly crucial because:
Static analysis helps you catch issues like type mismatches, memory leaks, null pointer dereferences, and incorrect API usage before your code runs. This proactive approach saves countless hours of debugging and prevents critical bugs from reaching production systems.
What Makes Coccinelle Special?
Coccinelle is a sophisticated pattern matching and transformation tool designed specifically for C code. Its name comes from the French word for “ladybug,” and like its namesake, it helps keep your code garden healthy by identifying and fixing problematic patterns.
Unlike simple text-based search and replace tools, Coccinelle understands the semantic structure of your code. This means it can recognize patterns regardless of formatting differences, making it incredibly powerful for large-scale code transformations.
Think of Coccinelle as a smart find-and-replace that understands C at the AST (Abstract Syntax Tree) level, not just text. This is what makes it fundamentally different from tools like sed or grep.
Real-World Use Cases
Coccinelle excels at:
How Coccinelle Fits Into Your Daily Workflow
As a driver developer, here is where Coccinelle slots in:
On Ubuntu/Debian Systems
sudo apt install coccinelle
From Source (Latest Features)
git clone https://github.com/coccinelle/coccinelle.git
cd coccinelle
make && sudo make install
After installation, verify it works:
spatch –version
Basic Integration with the Kernel Build System
Coccinelle integrates seamlessly with the kernel build system. Navigate to your kernel source directory and run:
Basic Check
make C=1 CHECK=”scripts/coccicheck” M=drivers/path/to/your/driver/
Comprehensive Check
make C=2 CHECK=”scripts/coccicheck” M=drivers/path/to/your/driver/
The C=1 flag performs basic analysis, while C=2 enables deeper, more thorough checking. The script will apply all available semantic patches and report any matches or potential issues.
When Coccinelle finds a match, it prints the file, line, and a diff showing what the semantic patch would change. Example output:
drivers/mydriver/mydriver.c:87:1-8: WARNING: NULL check before some freeing functions is
not needed.
– if (buf)
– kfree(buf);
+ kfree(buf);
This tells you that kfree() is null-safe, and the null check before it is redundant clutter. Coccinelle found this automatically using the null_check_before_free.cocci script from the kernel tree.
The SmPL Language
Coccinelle uses its own language called SmPL (Semantic Patch Language). A .cocci file defines a transformation rule: a pattern to match and optionally what to replace it with. The syntax is similar to a unified diff with metavariables for generalization.
Example 1: Detecting Missing Null Checks After kmalloc
This is the most common issue in early driver development — forgetting to check the return of kmalloc:
@@
expression E;
@@
E = kmalloc(…);
+ if (!E)
+ return -ENOMEM;
This semantic patch identifies kmalloc() calls and suggests adding null pointer checks. The @@ delimiters separate metavariable declarations from the pattern body. The + lines indicate what should be added.
Example 2: Replacing Deprecated API (devm_ migration)
When migrating a driver to use managed device resources, you need to replace kzalloc with devm_kzalloc and remove the corresponding kfree. Coccinelle can do this automatically:
@@
struct device *dev;
expression size, flags;
expression ptr;
@@
– ptr = kzalloc(size, flags);
+ ptr = devm_kzalloc(dev, size, flags);
…
– kfree(ptr);
This is extremely powerful. It removes the kfree call, too, because devm_ managed allocations are freed automatically on device removal. A text-based tool could never do this safely.
Example 3: Detecting Unchecked Return Values from request_irq
Forgetting to check the return value of request_irq() is a classic driver bug. The interrupt handler is never actually registered, and the driver appears to work until an interrupt fires and nothing handles it:
@@
expression irq, handler, flags, name, dev;
@@
– request_irq(irq, handler, flags, name, dev);
+ int ret = request_irq(irq, handler, flags, name, dev);
+ if (ret) {
+ dev_err(dev, “Failed to request IRQ: %d\n”, ret);
+ return ret;
+ }
Example 4: Lock/Unlock Imbalance Detection
This is a Coccinelle pattern to detect spin_lock without a corresponding spin_unlock on all paths:
@@
expression lock;
@@
spin_lock(&lock);
… when != spin_unlock(&lock);
return …;
The ‘when != spin_unlock’ clause tells Coccinelle to match only code paths where spin_unlock does NOT appear between the lock and the return. This is exactly the kind of semantic reasoning that no text tool can do.
Example 5: Your Own Driver’s Custom Rule
Suppose your team has a rule: always use your driver’s wrapper my_alloc() instead of kmalloc() directly. You can enforce it:
@@
expression size;
@@
– kmalloc(size, GFP_KERNEL)
+ my_alloc(size)
Save this as enforce_my_alloc.cocci and run it in CI. Any developer who accidentally uses kmalloc directly will get a warning automatically.
Example 6: Redundant Error Handling After platform_get_irq()
This pattern detects unnecessary error handling after calling platform_get_irq().
The function already prints an error message internally, so adding another dev_err() is redundant.
@@
expression pdev, irq;
@@
irq = platform_get_irq(pdev, …);
if (irq < 0) {
…
dev_err(…);
…
}
Example 7: Unnecessary .owner Field in Platform Driver
This pattern identifies redundant usage of .owner = THIS_MODULE inside a platform driver structure.
The kernel automatically assigns the owner field, so explicitly setting it is not required.
@@
identifier drv;
@@
static struct platform_driver drv = {
…
.driver = {
…
.owner = THIS_MODULE,
},
};
Example 8: Redundant NULL Check on Iterator in List Traversal
This pattern detects unnecessary NULL checks on iterator variables used in list_for_each_entry_safe.
The iterator is guaranteed to be valid during traversal, so checking if (pos) is redundant.
@@
identifier pos, n, head, member;
@@
list_for_each_entry_safe(pos, n, head, member) {
if (pos && …) {
…
}
}
Running Custom Semantic Patches
You can run a specific .cocci file directly with spatch:
# Run a single cocci script against your driver
spatch –sp-file enforce_my_alloc.cocci drivers/myvendor/mydriver.c
# Run against the whole driver directory
spatch –sp-file enforce_my_alloc.cocci –dir drivers/myvendor/mydriver/
# Apply changes in-place (use with caution, commit first!)
spatch –sp-file enforce_my_alloc.cocci –in-place drivers/myvendor/mydriver.c
Exploring the Kernel’s Built-In Cocci Scripts
The kernel source tree ships with hundreds of ready-made semantic patches:
ls /path/to/linux/scripts/coccinelle/
api/ – checks for correct API usage
iterators/ – loop pattern checks
locks/ – locking correctness
memory-funcs/ – allocation and deallocation patterns
misc/ – miscellaneous checks
null/ – null pointer issues
tests/ – test infrastructure
Start by exploring scripts/coccinelle/null/ and scripts/coccinelle/memory-funcs/. These are immediately useful for any driver developer and will teach you a lot about common pitfalls.
Case Study 1: Massive API Migration in a Real Kernel
When the kernel’s DMA API changed from dma_alloc_coherent() to a newer pattern, thousands of driver files across the kernel tree needed updating. Coccinelle semantic patches helped automatically update all those calls. What would have taken weeks of manual work was completed in hours with high confidence.
This is the most famous use case for Coccinelle and why kernel maintainers love it. Entire subsystem API changes are now announced with an accompanying .cocci file so driver authors can run the transformation themselves.
Case Study 2: Your DMA Driver’s Error Path Consistency
In DMA driver development (which you’re familiar with), a common pattern is:
buf = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
if (!buf)
return -ENOMEM;
But sometimes developers forget the check, or they check with if (buf == NULL) instead of the canonical if (!buf). Coccinelle can enforce the exact pattern your team wants:
@@
expression dev, size, handle, flags;
expression ptr;
@@
ptr = dma_alloc_coherent(dev, size, &handle, flags);
– if (ptr == NULL)
+ if (!ptr)
Case Study 3: Enforcing devm_ Usage in New Drivers
Modern Linux drivers should use managed resources (devm_ prefix) to simplify error paths. Coccinelle can warn whenever a driver’s probe() function uses plain kzalloc instead of devm_kzalloc:
@@
identifier probe;
expression size, flags;
@@
probe(…) {
…
– kzalloc(size, flags)
+ devm_kzalloc(dev, size, flags)
…
}
Issue 1: Null Check Before kfree
Problem: kfree() is already null-safe, so wrapping it in an if (ptr) check is unnecessary clutter.
Code with the issue:
if (priv->dma_buf)
kfree(priv->dma_buf);
What Coccinelle reports:
WARNING: NULL check before kfree is not needed.
Issue 2: Using Obsolete API
Problem: Driver uses an old API like init_MUTEX() which was removed in favor of sema_init():
init_MUTEX(&dev->sem); // Coccinelle: obsolete API
Issue 3: Missing Error Check on platform_get_resource
A very common driver bug: forgetting to check if platform_get_resource() returned NULL:
struct resource *res;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// No null check!
base = ioremap(res->start, resource_size(res)); // Crash if res is NULL
Coccinelle has a script for exactly this pattern. The fix:
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, “Failed to get memory resource\n”);
return -EINVAL;
}
Pre-Commit Hook
Automate Coccinelle checking with a Git pre-commit hook:
#!/bin/bash
# .git/hooks/pre-commit
DRIVER_PATH=”drivers/myvendor/mydriver”
KERNEL_DIR=”/path/to/linux”
echo “Running Coccinelle checks…”
cd $KERNEL_DIR
make C=1 CHECK=”scripts/coccicheck” M=$DRIVER_PATH 2>&1 | grep -i warning
if [ $? -ne 0 ]; then
echo “Coccinelle checks failed! Fix warnings before committing.”
exit 1
fi
echo “Coccinelle: all good!”
Performance Considerations
Coccinelle can be slower than Sparse, adding 50-100% to compilation time depending on the number of semantic patches. Here are strategies to manage this:
Coccinelle is uniquely powerful for driver developers because it understands C at a semantic level. You can use it to:
Up Next: Blog 2 of 3
Sparse — The Type System Guardian
Learn how Sparse enforces kernel-specific pointer annotations like __user, __iomem, and __rcu,
catches endianness bugs, and prevents address space violations that the compiler silently ignores.
If you write any driver that touches hardware registers or user space, Sparse is non-negotiable.
Happy coding, and may your drivers always be bug-free!