What is Linux Clock Lookup Unregistration Guide in Linux Kernel

Linux Clock Lookup Unregistration Guide-Free Linux Device Drivers Course
Free Linux Kernel Development Course — Common Clock Framework, Part 3
🎓 Free Linux Kernel Course
🧩 Free Linux Device Drivers Course
⚙️ Free Embedded Systems Course

If you are following our free embedded Linux course, you already know that a clock provider driver must register its clocks with the Common Clock Framework (CCF) before any consumer can use them. But registration alone is not enough. Somewhere the kernel needs a way to look up a clock by name, and just as importantly, a way to safely tear it down when a device is removed or probing fails. This lecture on linux clock unregistration api and clock lookup completes that picture by walking through the internal struct clk_lookup, the modern clk_hw_register_clkdev() helper, and the full set of clock unregistration functions available in the current kernel.

This is lecture 3 of our Common Clock Framework series, part of the growing free linux device drivers course and free linux kernel development course available on EmbeddedPathashala. If you have not read the earlier lectures on CCF data structures and clock provider registration, we recommend going through those first.

What You Will Learn

  • How the kernel resolves a clock lookup using struct clk_lookup
  • Why clk_hw_register_clkdev() replaced the older name-based registration approach
  • The difference between clk_hw_register() and clk_hw_register_clkdev()
  • All four clock unregistration APIs and when each one is required
  • How to write, build, and test a driver that safely unregisters its clock
  • Common mistakes engineers make while tearing down clock providers

Prerequisites

  • A working Linux kernel build environment (kernel 6.x recommended)
  • Basic familiarity with platform drivers (probe()/remove())
  • Completion of our earlier lectures on CCF data structures and clock provider registration
  • Basic C programming knowledge

Why Clock Lookup Still Matters

Before the device tree became the default way to describe hardware, a clock provider had no automatic way to tell a consumer driver which clock belonged to it. The kernel solved this with a small lookup table. Every time a clock was registered for name-based lookup, an entry was added to a global list that mapped a device identifier and a connection identifier to an actual clock. Even today, on platforms that still rely on board files or on legacy non-device-tree code paths, this lookup mechanism is alive and well inside the CCF core.

struct clk_lookup: The Core Data Structure

At the heart of this mechanism sits struct clk_lookup. Conceptually it is a very small structure, but each field plays an important role in how the kernel resolves a clock lookup request.

struct clk_lookup {
    struct list_head node;
    const char *dev_id;
    const char *con_id;
    struct clk *clk;
    struct clk_hw *clk_hw;
};
How a Clock Lookup Entry Is Resolved
Consumer calls
clk_get(dev, con_id)
Kernel searches
global clocks list
Match found on
dev_id + con_id
Returns associated
struct clk

Here is what each field means in plain language:

  • node — the list entry used to link this lookup into the kernel’s global clocks list
  • dev_id — usually the device name, used to identify which device this clock belongs to
  • con_id — the “connection id”, a string that names the specific clock line (for example “uart_clk” or NULL for a default clock)
  • clk — the legacy consumer-facing clock pointer
  • clk_hw — the modern provider-facing clock handle

Internally, whenever a lookup entry is added, the kernel simply appends it to a protected linked list, guarded by a mutex so that concurrent registration from multiple drivers stays safe:

static void __clkdev_add(struct clk_lookup *cl)
{
    mutex_lock(&clocks_mutex);
    list_add_tail(&cl->node, &clocks);
    mutex_unlock(&clocks_mutex);
}

You will rarely call this function directly. It is invoked internally by the registration helper we look at next.

Registering a Name-Based Clock Lookup

The modern, recommended function for registering a name-based clock lookup entry is clk_hw_register_clkdev(). It wraps a lower-level legacy function internally, but as a driver author you should only ever call the clk_hw-based version.

int clk_hw_register_clkdev(struct clk_hw *hw,
                            const char *con_id,
                            const char *dev_id);
Parameter Meaning
hw The already-registered clk_hw handle for this clock
con_id Connection id string consumers will request, or NULL
dev_id Device name string this clock is bound to

clk_hw_register() vs clk_hw_register_clkdev(): Don’t Confuse Them

These two function names look almost identical, which trips up a lot of beginners in our free embedded systems course. They solve two completely different problems:

Function Purpose
clk_hw_register() Registers the clock itself in the Common Clock Framework core
clk_hw_register_clkdev() Registers a lookup entry so a consumer can later find that already-registered clock by name

Important note: On a device-tree-only platform, you almost never need clk_hw_register_clkdev(). Device tree consumers resolve clocks through of_clk_add_hw_provider() instead (covered in the next lecture). Only reach for name-based lookup when you have a genuine legacy or non-DT use case.

Linux Clock Unregistration API: Tearing Down a Clock Safely

Every clock a driver registers must eventually be unregistered — either because the underlying hardware is removed, or because something failed midway during probe() and the driver needs to unwind cleanly. Getting this wrong is one of the most common sources of use-after-free bugs in provider drivers, so understanding the full linux clock unregistration api surface is essential.

The Four Unregistration Functions

Function Targets When to Use
clk_hw_unregister(struct clk_hw *hw) clk_hw-based clocks Manual cleanup of a modern clock, e.g. in a plain remove() callback
clk_unregister(struct clk *clk) Legacy clk-based clocks Only in old drivers still using the legacy consumer-style registration
devm_clk_hw_unregister(struct device *dev, struct clk_hw *hw) clk_hw-based clocks Devres-managed cleanup when the clock was registered with a plain (non-devm) call but you still want automatic teardown
devm_clk_unregister(struct device *dev, struct clk *clk) Legacy clk-based clocks Devres-managed cleanup for legacy clocks

Tip: If you originally registered your clock with devm_clk_hw_register(), the Devres core already unregisters it automatically on driver detach — you do not need to call clk_hw_unregister() yourself. Manual unregistration functions exist mainly for clocks registered with the plain, non-managed variants, or for early error-path cleanup inside probe().

Choosing the Right Unregistration Function
Did you register with a devm_* function?
YES
Devres handles it — no manual call needed
NO
Call clk_hw_unregister() or clk_unregister() yourself in remove()

Hands-On Example: ep_clk_unregister_demo

Let’s build a small, original platform driver that registers a fixed-rate clock along with a name-based clock lookup entry, and then correctly unregisters both on removal. This driver is written from scratch for this lecture and targets modern kernel 6.x APIs.

// ep_clk_unregister_demo.c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include <linux/of.h>

struct ep_clk_priv {
    struct clk_hw hw;
};

static const struct clk_ops ep_clk_ops = {
    .recalc_rate = NULL, /* fixed rate demo, using clk_hw_register_fixed_rate below instead */
};

static int ep_clk_unregister_demo_probe(struct platform_device *pdev)
{
    struct clk_hw *hw;
    int ret;

    /* Step 1: register a simple fixed-rate clock at 24 MHz */
    hw = clk_hw_register_fixed_rate(&pdev->dev, "ep_demo_clk", NULL, 0, 24000000);
    if (IS_ERR(hw)) {
        dev_err(&pdev->dev, "failed to register ep_demo_clk\n");
        return PTR_ERR(hw);
    }

    /* Step 2: register a name-based lookup so a consumer can find it */
    ret = clk_hw_register_clkdev(hw, "ep_uart_clk", "ep-uart-consumer");
    if (ret) {
        dev_err(&pdev->dev, "failed to register clkdev lookup\n");
        clk_hw_unregister_fixed_rate(hw);
        return ret;
    }

    platform_set_drvdata(pdev, hw);
    dev_info(&pdev->dev, "ep_clk_unregister_demo: clock registered at 24MHz\n");
    return 0;
}

static int ep_clk_unregister_demo_remove(struct platform_device *pdev)
{
    struct clk_hw *hw = platform_get_drvdata(pdev);

    clk_hw_unregister_fixed_rate(hw);
    dev_info(&pdev->dev, "ep_clk_unregister_demo: clock unregistered cleanly\n");
    return 0;
}

static const struct of_device_id ep_clk_unregister_demo_of_match[] = {
    { .compatible = "ep,clk-unregister-demo" },
    { }
};
MODULE_DEVICE_TABLE(of, ep_clk_unregister_demo_of_match);

static struct platform_driver ep_clk_unregister_demo_driver = {
    .probe = ep_clk_unregister_demo_probe,
    .remove = ep_clk_unregister_demo_remove,
    .driver = {
        .name = "ep_clk_unregister_demo",
        .of_match_table = ep_clk_unregister_demo_of_match,
    },
};
module_platform_driver(ep_clk_unregister_demo_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("EmbeddedPathashala");
MODULE_DESCRIPTION("Demo driver for clk_hw_register_clkdev and unregistration");

Matching Device Tree Node

ep_clk_demo: ep-clk-demo {
    compatible = "ep,clk-unregister-demo";
};

Build and Load Steps

make -C /lib/modules/$(uname -r)/build M=$PWD modules
sudo insmod ep_clk_unregister_demo.ko
dmesg | tail -n 5
sudo rmmod ep_clk_unregister_demo
dmesg | tail -n 5

Expected Output

[ 1234.001122] ep_clk_unregister_demo: clock registered at 24MHz
...
[ 1240.556677] ep_clk_unregister_demo: clock unregistered cleanly

You can also verify the clock briefly appeared in the clock tree while the module was loaded:

cat /sys/kernel/debug/clk/clk_summary | grep ep_demo_clk

Common Mistakes with Clock Unregistration

  • Forgetting to unregister in an error path — if clk_hw_register_clkdev() fails after a successful clk_hw_register(), you must still unregister the clock before returning an error, exactly as shown above.
  • Mixing legacy and modern APIs — never call clk_unregister() on a handle obtained from a clk_hw_register() family function, and vice versa.
  • Double unregistration — calling both a manual unregister function and relying on devm cleanup for the same clock will cause a use-after-free.
  • Ignoring the return value of registration functions — always check with IS_ERR() before proceeding.

Best Practices

  • Prefer the devm_* variants whenever the clock’s lifetime matches the device’s lifetime.
  • Only add a clkdev lookup entry if you have a genuine non-DT consumer; otherwise rely on the DT provider mechanism.
  • Always unwind clock registration in the reverse order of registration during error handling.
  • Use clk_summary in debugfs during development to confirm clocks appear and disappear as expected.

Security and Performance Considerations

Clock unregistration itself is not commonly a security-sensitive path, but leaving stale clkdev lookup entries behind after a module unload can cause a later consumer driver to dereference a freed clk_hw pointer, leading to a kernel panic. From a performance standpoint, the clocks list and lookup list are protected by mutexes, so registering or unregistering a very large number of clocks in a tight loop (uncommon in real drivers) can introduce lock contention; this is rarely an issue for normal driver probe/remove flows.

Real-World Use Case

A typical SoC PMIC (power management IC) driver registers several regulator-derived clocks during probe. If the PMIC is removed (for example, on a hot-pluggable expansion board), the driver’s remove() callback must unregister every clock it registered, in the correct order, so that dependent peripheral drivers do not reference a dangling clock handle. This exact pattern — register, expose to consumers, and cleanly unregister — is why understanding the full linux clock unregistration api matters for production-quality drivers.

Summary and Key Takeaways

  • struct clk_lookup maps a dev_id/con_id pair to a clock for name-based consumer lookup.
  • clk_hw_register_clkdev() is the modern way to add such a lookup entry; it is rarely needed on device-tree-only platforms.
  • clk_hw_register() registers the clock itself; clk_hw_register_clkdev() only registers a way to find it — they are not interchangeable.
  • Four unregistration functions exist: clk_hw_unregister(), clk_unregister(), and their devm_* managed counterparts.
  • Devres-managed clocks are unregistered automatically; manually registered clocks must be unregistered explicitly in remove() or in error paths.

Conclusion

Clock lookup and clock unregistration are two sides of the same coin: one lets a consumer find a clock, and the other guarantees that once a clock provider goes away, nothing in the kernel is left holding a dangling reference to it. Mastering the linux clock unregistration api alongside the older name-based lookup mechanism gives you a complete, production-ready understanding of clock provider lifecycle management. In the next lecture of this free linux kernel development course, we move to the device-tree-based side of exposing clocks with struct of_clk_provider and of_clk_add_hw_provider().

Frequently Asked Questions

What is the difference between clk_hw_register() and clk_hw_register_clkdev()?

clk_hw_register() creates the clock in the Common Clock Framework core. clk_hw_register_clkdev() only adds a name-based lookup entry so a consumer without device tree support can find an already-registered clock.

Do I need clk_hw_register_clkdev() on a device tree platform?

Usually not. Device tree consumers resolve clocks through of_clk_add_hw_provider(), which we cover in the next lecture. Name-based lookup is mainly kept for legacy or non-DT boards.

What happens if I forget to unregister a clock in remove()?

The clock and its lookup entry remain in the kernel’s internal lists even though the underlying hardware or module is gone, which can lead to a later consumer dereferencing a stale pointer and crashing the kernel.

Should I use devm_clk_hw_unregister() or clk_hw_unregister()?

Use the devm variant if you want the Devres core to automatically unregister the clock when the device detaches. Use the plain function when you need explicit control, such as in an early error path inside probe().

Can I mix clk_unregister() with a clk_hw_register()-based clock?

No. Legacy and modern clock APIs use different underlying handles. Always match clk_hw_register() with clk_hw_unregister(), and legacy clk_register() with clk_unregister().

What does the con_id field in struct clk_lookup actually do?

It is a string identifying a specific clock line for a device, allowing one device to have multiple named clocks looked up independently. It can be NULL if the device only has a single default clock.

Is struct clk_lookup something I need to fill in manually?

No. You should always go through clk_hw_register_clkdev(), which fills in and manages the clk_lookup structure internally.

{
“@context”: “https://schema.org”,
“@type”: “FAQPage”,
“mainEntity”: [
{“@type”: “Question”, “name”: “What is the difference between clk_hw_register() and clk_hw_register_clkdev()?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “clk_hw_register() creates the clock in the Common Clock Framework core. clk_hw_register_clkdev() only adds a name-based lookup entry so a consumer without device tree support can find an already-registered clock.”}},
{“@type”: “Question”, “name”: “Do I need clk_hw_register_clkdev() on a device tree platform?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Usually not. Device tree consumers resolve clocks through of_clk_add_hw_provider(). Name-based lookup is mainly kept for legacy or non-DT boards.”}},
{“@type”: “Question”, “name”: “What happens if I forget to unregister a clock in remove()?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “The clock and its lookup entry remain in the kernel’s internal lists even though the hardware is gone, which can lead to a crash when a later consumer dereferences a stale pointer.”}},
{“@type”: “Question”, “name”: “Should I use devm_clk_hw_unregister() or clk_hw_unregister()?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “Use the devm variant for automatic cleanup on device detach. Use the plain function for explicit control, such as in an early probe() error path.”}},
{“@type”: “Question”, “name”: “Can I mix clk_unregister() with a clk_hw_register()-based clock?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “No, always match the legacy and modern API families consistently.”}},
{“@type”: “Question”, “name”: “What does the con_id field in struct clk_lookup actually do?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “It identifies a specific named clock line for a device, allowing multiple clocks per device to be looked up independently.”}},
{“@type”: “Question”, “name”: “Is struct clk_lookup something I need to fill in manually?”, “acceptedAnswer”: {“@type”: “Answer”, “text”: “No, clk_hw_register_clkdev() manages it internally.”}}
]
}

Related Free Courses on EmbeddedPathashala

free embedded systems course
free linux development course
free linux device drivers course
free linux kernel development course
linux clock unregistration api

Continue Your Free Linux Kernel Journey

Explore more lectures in our free embedded Linux and device driver course series.

Leave a Reply

Your email address will not be published. Required fields are marked *