
6 minute read
Kernel Uevent: How Information is Passed from Kernel to User Space
from n Source ...ber 2012
by Hiba Dweib
Kernel Uevent
How Information is Passed from Kernel to User Space
Advertisement
This article explores the kernel uevent framework and looks at how drivers use it to send device-specific information to user space. It also covers how to receive and process kernel uevents in user space.
The Linux kernel version 2.6.10 (and later versions) introduced the uevent notification mechanism for kernel and user-space communication. These user events (uevents) generated from the kernel are used by userspace daemons to either create/ remove device files, to run programs, or load/remove a driver in user land. Inside the kernel, these uevents are linked to the kernel data structure called kobject. This data structure’s life cycle (linked with a device) is what is notified as uevents to user space.
Netlink
The Linux kernel uses netlink to send kernel uevents to the user space. Netlink is a socket-like mechanism used in Linux to pass information between kernel and user processes. Netlink, similar to a generic BSD socket infrastructure, supports primitive APIs like socket(), bind(), sendmsg() and recvmsg().
Uevent actions
As discussed above, a uevent message communicates the device’s or subsystems’ states inside the kernel to user space. The kobject_action data structure indicates the kernel object’s state, and is defined in include/linux/kobject.h as enum kobject_action:
enum kobject_action { KOBJ_ADD, KOBJ_REMOVE, KOBJ_CHANGE, KOBJ_MOVE, KOBJ_ONLINE, KOBJ_OFFLINE, KOBJ_MAX };
Each uevent message sent to user space is tagged with one of these kobject actions. For example, actions like KOBJ_ADD or KOBJ_REMOVE are used to notify the addition or deletion of a kernel object—which happen when a device is added or deleted inside the kernel, using device_add or device_del. KOBJ_CHANGE is the action most used by drivers to notify changes in the device, like the configuration or state. Other actions like KOBJ_ONLINE/KOBJ_OFFLINE are generally
used to indicate when a CPU’s state changed; KOBJ_MOVE is used only by network interfaces when renaming a kobject.
You can find the kernel uevent framework implemented in lib/kobject_uevent.c. The uevent framework exports APIs kobject_uevent and kobject_uevent_env, which implement the user-space communication. The difference between the two APIs is that the latter allows a driver to send extra information to user space. This extra information is termed ‘environmental data’ by the framework. Internal to the framework, the kobject_uevent API is just a call of the kobject_uevent_env API with ‘environmental data’ set to NULL.
The basic message passed to user space from the kernel uevent framework is as follows:
ACTION= DEVPATH= SUBSYSTEM= SEQNUM=
The DEVPATH string holds the path of the kobject and SUBSYSTEM indicates the subsystem the uevent originated from. Any extra data other than the above four, is termed as environment data. Information in this environment data is specific to the subsystem or the kernel object. The environment data, which is basically an array of strings, holds state changes or other information useful to user-space code. The environment data is packed in a kobj_uevent_env data structure, defined in /include/linux/kobject.h:
struct kobj_uevent_env { char *envp[UEVENT_NUM_ENVP]; int envp_idx; char buf[UEVENT_BUFFER_SIZE]; int buflen; };
The uevent framework provides the add_uevent_var API to add more environment data to a uevent message.
How it is used by a driver
Let us now see how a driver uses this framework to send information to user space, with an example—a simple scenario of an Android device connected to a host PC as a USB device. When an Android device is connected as a USB device, the Android framework notifies the user with a USB icon in the notification bar. To do this, the Android USB framework needs certain information from the kernel when the Android device is configured as a USB device. This information is passed on through uevents by the kernel USB driver. Let us see how USB-specific state information is passed on to the Android USB framework.
The following code is from the android_work function of the drivers/usb/gadget/android.c file; you can browse the code at https://android.googlesource.com/. The USB gadget driver forms three different state strings with the keyword USB_STATE, in a format similar to kobj_uevent_env environment data, as shown below:
User Space Application
Netlink Socket
Kernel Driver
Figure 1: A simple representation of the uevent mechanism
Kernel driver sends uevent message
netlink driver broadcasts the uevent messages
User Space client processes uevent messages
Figure 2: The flow of uevents from kernel to user space
char *disconnected[2] = { "USB_STATE=DISCONNECTED", NULL }; char *connected[2] = { "USB_STATE=CONNECTED", NULL }; char *configured[2] = { "USB_STATE=CONFIGURED", NULL }; char **uevent_envp = NULL;
When an Android device is connected as a USB device, the state of the device is checked from the gadget driver’s flags and appropriate environment data is assigned to the uevent_envp variable. For example, when the device is connected to the PC, USB_STATE=CONNECTED is set and when the drivers are installed successfully and the device is functional, USB_STATE=CONFIGURED is set.
if (cdev->config) uevent_envp = configured; else if (dev->connected != dev->sw_connected) uevent_envp = dev->connected ? connected : disconnected;
This state information is then propagated to the user space using kobject_uevent_env with the action KOBJ_CHANGE, as shown below:
if (uevent_envp) {
kobject_uevent_env(&dev->dev->kobj, KOBJ_CHANGE, uevent_ envp); pr_info("%s: sent uevent %s\n", __func__, uevent_envp[0]);
In the user space, Android’s UsbDeviceManager collects this USB_STATE information and propagates it to the listeners. You can check out the UsbDeviceManager.java code that handles this in the repository: http://bit.ly/R2fCLP.
After that example, let us write a simple C program to see how to receive uevents in user space.
Receiving uevents in user space
Since the kernel uevent uses a netlink socket, it requires a simple socket program in user space to receive uevent messages. Figure 2 illustrates a simple uevent flow through kernel space to the user space. The best example of how to receive and decode uevents in user space is available as open source (uevent_listen.c) and we will use it to explain things.
The first step to receiving a uevent in user space is to connect to or open the netlink socket. In this case, it is NETLINK_KOBJECT_UEVENT. This allows us to receive the kernel messages.
sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT); if (sock == -1) { printf("error getting socket, exit\n"); exit(1); }
retval = bind(sock, (struct sockaddr *) &snl, sizeof(struct sockaddr_nl)); if (retval < 0) { printf("bind failed, exit\n"); goto exit; }
After successfully binding with the NETLINK_ KOBJECT_UEVENT, the program should wait, listening to the uevent socket for messages from the kernel. The maximum buffer size of the uevent message is 2048, as defined in /include/linux/kobject.h, and the receive buffer will be of the same size.
buflen = recv(sock, &buffer, sizeof(buffer), 0); if (buflen < 0) { printf("error receiving message\n"); continue; }
The received message holds multiple string data created by the kernel uevent framework, and parsing can be based on the length returned by the socket API.
/* hotplug events have the environment attached - reconstruct envp[] */ for (i = 0; (bufpos < (size_t)buflen) && (i < HOTPLUG_NUM_ENVP-1); i++) { int keylen; char *key; key = &buffer[bufpos]; keylen = strlen(key); envp[i] = key; bufpos += keylen + 1; } envp[i] = NULL;
printf("[%i] received '%s' from '%s'\n", time(NULL), action, devpath);
/* print payload environment */ for (i = 0; envp[i] != NULL; i++) printf("%s\n", envp[i]);
Having seen important blocks of uevent_listen.c, let us build and run the program on the terminal. The following snip shows one of the uevents received when you connect a USB pen drive to your PC while running uevent_listen.out:
[1345998474] received 'add' from '/devices/ pci0000:00/0000:00:1d.7/usb2/2-1' ACTION=add DEVPATH=/devices/pci0000:00/0000:00:1d.7/usb2/2-1 SUBSYSTEM=usb MAJOR=189 MINOR=149 DEVNAME=bus/usb/002/022 DEVTYPE=usb_device PRODUCT=cf2/6230/100 TYPE=0/0/0 BUSNUM=002 DEVNUM=022 SEQNUM=2548
To understand how the following message was constructed, you can refer to the usb_dev_uevent (drivers\usb\core\usb.c) and usb_uevent (drivers\usb\core\driver.c) functions.
This article provided a very brief introduction of the Linux uevent notification mechanism, followed by a simple user space program that receives uevents in user space. There are many more interesting features you can explore like uevent filters, uevent files for cold-plugged devices, udev rules, etc, to improve your understanding of the uevent mechanism.
By: Rajaram Regupathy
The author works as a Linux specialist with ST Ericsson India Pvt Ltd, and is the author of the book ‘Bootstrap Yourself with Linux-USB Stack’.