I am studying Linux device drivers, my main focus is on wifi drivers. I want to know how the code flows when I plugin my device. Maybe, I can do something like add a printk line in every function. The device I have is supported by ath9k_htc driver. I want to make some changes in the driver code for learning purposes.

What is the correct or general approach for understanding the code flow of driver modules in linux?

  • WiFi (networking in general) is a very complex area... – vonbrand Mar 1 '20 at 22:05

When I want to do this, I use the ftrace framework. Start by mounting the special file system:

mount -t tracefs nodev /sys/kernel/tracing

(as root; you should become root for all this, you’ll be doing everything as root anyway, and it’s easier to have a root shell than to use sudo).

Then change to that directory:

cd /sys/kernel/tracing

It contains a basic README which provides a short summary. To explore function calls, I use the function graph tracer, function_graph in available_tracers. Identify the functions you’re interested in, for example ath9k_htc_tx, and set them up

echo ath9k_htc_tx > set_graph_function

You can append other functions, make sure to use >> after the first function. You can see the configured functions with

cat set_graph_function

When you write to set_graph_function, the function is checked against the running kernel; if the function can’t be found, the write will fail, so you’ll know straight away if you’ll end up not tracing anything.

Once the functions are set up, enable the tracer:

echo function_graph > current_tracer

then watch the trace file. To disable the tracer again,

echo nop > current_tracer

or flip tracing_on by writing 0 or 1 to it (0 to disable tracing, 1 to re-enable it).


The main takeaway is that drivers connect an upper and a lower interface together. In the case of ath9k_htc, the lower interface is USB, the upper interface is the network stack.

Control flow is largely a state machine that can react to events arriving from both interfaces without any synchronization, for example it is entirely possible for the network interface to notify the driver to join a multicast group, and at the same time a notification comes in from the USB subsystem that the device was ejected -- in multiprocessor systems, these events can be reported on different CPUs, and the driver will be entered concurrently then.

Most drivers do not have a process or thread context and are purely event-driven, with the event loop external to the driver, so usually you see many small functions handling specific events, attempting to forward them, making a note somewhere if that fails, and immediately returning.

The most sensible way to visualize control flow in a typical driver is to draw a map of shared lockable resources and which functions access them, as a typical line of communication is to lock a list, append data to it, and unlock. If you draw USB related functions on one side, and network related functions on the other of the resources, that should give you the clearest picture.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.