0% found this document useful (0 votes)
124 views32 pages

Linux Kernel Driver

The document provides an introduction to Linux kernel driver programming. It discusses the need for a device model to support drivers across different hardware architectures and devices. It describes the main data structures in the Linux device model, including bus_type, device_driver and device structures. It provides examples of registering USB and platform drivers and devices. It explains how drivers are matched to devices and the role of the probe function in initializing devices.

Uploaded by

scuser.poke
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
124 views32 pages

Linux Kernel Driver

The document provides an introduction to Linux kernel driver programming. It discusses the need for a device model to support drivers across different hardware architectures and devices. It describes the main data structures in the Linux device model, including bus_type, device_driver and device structures. It provides examples of registering USB and platform drivers and devices. It explains how drivers are matched to devices and the role of the probe function in initializing devices.

Uploaded by

scuser.poke
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 32

Introduction to Linux kernel

driver programming
Introduction to Linux kernel driver
programming

The Linux kernel device model


Authors and license

Authors
– Michael Opdenacker ([email protected])
Founder of Bootlin,
kernel and embedded Linux engineering company
https://bootlin.com/company/staff/michael-opdenacker

License
– Creative Commons Attribution – Share Alike 4.0
https://creativecommons.org/licenses/by-sa/4.0/
– Document sources: https://github.com/e-ale/Slides
Need for a device model

For the same device, need to use the same device driver on
multiple CPU architectures (x86, ARM…), even though the
hardware controllers are different.

Need for a single driver to support multiple devices of the
same kind.

This requires a clean organization of the code, with the device
drivers separated from the controller drivers, the hardware
description separated from the drivers themselves, etc.
Driver: between bus infrastructure and
framework
In Linux, a driver is always interfacing with:

a framework that allows the driver to
expose the hardware features in a
generic way.

a bus infrastructure, part of the device
model, to detect/communicate with the
hardware.
Let’s focus on the bus infrastructure for now
Device model data structures
The device model is organized around three main data structures:
● The struct bus_type structure, which represent one type of
bus (USB, PCI, I2C, etc.)
● The struct device_driver structure, which represents one driver
capable of handling certain devices on a certain bus.
● The struct device structure, which represents one device connected to a
bus
The kernel uses inheritance to create more specialized versions of struct
device_driver and struct device for each bus subsystem.
The bus driver
● Example: USB. Implemented in drivers/usb/core/
● Creates and registers the bus_type structure

Provides an API to register and implement adapter drivers
(here USB controllers), able to detect the connected devices and allowing to
communicate with them.

Provides an API to register and implement device drivers (here USB device
drivers)

Matches the device drivers against the devices detected by the adapter drivers.
● Defines driver and device specific structures, here mainly struct usb_driver
and struct usb_interface
USB bus example

A single driver for compatible devices, though connected to buses with different controllers.
Device drivers (1)
Need to register supported devices to the bus core.

Example: drivers/net/usb/rtl8150.c
static struct usb_device_id rtl8150_table[] =
{{ USB_DEVICE(VENDOR_ID_REALTEK, PRODUCT_ID_RTL8150) },
{ USB_DEVICE(VENDOR_ID_MELCO, PRODUCT_ID_LUAKTX) },
{ USB_DEVICE(VENDOR_ID_MICRONET, PRODUCT_ID_SP128AR) },
{ USB_DEVICE(VENDOR_ID_LONGSHINE, PRODUCT_ID_LCS8138TX) },[…]
{}
};
MODULE_DEVICE_TABLE(usb, rtl8150_table);
Device drivers (2)
Need to register hooks to manage devices (newly detected or removed ones), as
well as to react to power management events (suspend and resume)

static struct usb_driver rtl8150_driver = {


.name = "rtl8150",
.probe = rtl8150_probe,
.disconnect = rtl8150_disconnect,
.id_table = rtl8150_table,
.suspend = rtl8150_suspend,
.resume = rtl8150_resume
};
Device drivers (3)
The last step is to register the driver structure to the bus core.

static int __init usb_rtl8150_init(void)


{
return usb_register(&rtl8150_driver);
}
static void __exit usb_rtl8150_exit(void)
{
usb_deregister(&rtl8150_driver);
}
module_init(usb_rtl8150_init);
module_exit(usb_rtl8150_exit);
Note: this code has now been replaced by a shorter module_usb_driver() macro.

Now the bus driver knows the association between the devices and the device driver.
Work in the probe() function
probe() is called for each newly matched device

Initialize the device

Prepare driver work: allocate a structure for a suitable
framework, allocate memory, map I/O memory, register
interrupts…

When everything is ready, register the new device to
the framework.
At driver loading time

The USB adapter driver that
corresponds to the USB controller
registers itself to the USB core
● The rtl8150 USB device driver
registers itself to the USB core

The USB core now knows the
association between the vendor/product
IDs of rtl8150 and the struct
usb_driver structure of this driver
When a device is detected
The model is recursive

Adapter drivers are device drivers too!


Platform devices and drivers

Want to use the Device Model for devices that are not on buses that
can auto-detect devices (very frequent in embedded systems)

Examples: UARTs, flash memory, LEDs, GPIOs, MMC/SD,
Ethernet…

Solution:
1) Provide a description of devices
2) Manage them through a fake bus: the platform bus.
3) Drive the platform devices
Describing non-detectable devices

Description through a Device Tree (on ARM, PowerPC, ARC…)
● In arch/arm/boot/dts/ on ARM

Two parts:
– Device Tree Source (.dts)
One per board to support in the Linux kernel
Advantage: no need to write kernel code to support a new board
(if all devices are supported).
– Device Tree Source Includes (.dtsi)
Typically to describe devices on a particular SoC,
or devices shared between similar SoCs or boards

Other method for describing non-detectable devices: ACPI
(on x86 platforms). Not covered here.
Declaring a device: .dtsi example
Label Node name

...
i2c0: i2c@44e0b000 {
compatible = "ti,omap4-i2c"; Compatible drivers
#address-cells = <1>;
#size-cells = <0>;
Node ti,hwmods = "i2c1";
properties reg = <0x44e0b000 0x1000>; HW register start
interrupts = <70>; address and range
status = "disabled"; Present but not
}; used by default
...

From arch/arm/boot/dts/am33xx.dtsi
Instantiating a device: .dts example
&i2c0 { Pin muxing configuration
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;
(routing to external package pins)
Phandle
status = "okay"; Enabling this device, otherwise ignored
(reference clock-frequency = <400000>; Node property: frequency
to label)
tps: tps@24 {
reg = <0x24>;
};

baseboard_eeprom: baseboard_eeprom@50 {
List of devices on
compatible = "at,24c256"; i2c0
reg = <0x50>; I2C bus identifier
#address-cells = <1>;
#size-cells = <1>;
baseboard_data: baseboard_data@0 {
reg = <0 0x100>;
};
};
}; From arch/arm/boot/dts/am335x-boneblue.dts
Pin multiplexing

Modern SoCs have too many
hardware blocks compared to physical
pins exposed on the chip package.

Therefore, pins have to be multiplexed

Pin configurations are defined in the
Device Tree

Correct pin multiplexing is mandatory
to make a device work from an
electronic point of view.
DT pin definitions
&am33xx_pinmux {
...
i2c0_pins: pinmux_i2c0_pins {
pinctrl-single,pins = <
AM33XX_IOPAD(0x988, PIN_INPUT_PULLUP | MUX_MODE0) /* (C17) I2C0_SDA.I2C0_SDA */
AM33XX_IOPAD(0x98c, PIN_INPUT_PULLUP | MUX_MODE0) /* (C16) I2C0_SCL.I2C0_SCL */
>;
};

}; Register offset corresponding Allows to select Package pin SoC signal
to a given package pin a given SoC signal name name
... Configures the pin:
input, output, drive
&i2c0 { strengh, pull up/down...
pinctrl-names = "default";
pinctrl-0 = <&i2c0_pins>;

status = "okay";
clock-frequency = <400000>;
... From arch/arm/boot/dts/am335x-boneblue.dts
};
DT: matching devices and drivers
Platform drivers are matched with platform devices that have the same compatible
property. static const struct of_device_id omap_i2c_of_match[] = {
{
.compatible = "ti,omap4-i2c",
.data = &omap4_pdata,
},
{

};

static struct platform_driver omap_i2c_driver = {
.probe = omap_i2c_probe,
.remove = omap_i2c_remove,
.driver = {
.name = "omap_i2c",
.pm = OMAP_I2C_PM_OPS,
.of_match_table = of_match_ptr(omap_i2c_of_match),
},
};
From drivers/i2c/busses/i2c-omap.c
Usage of the platform bus
Like for physical buses, the platform bus is used by the driver to retrieve information
about each device
static int omap_i2c_probe(struct platform_device *pdev)
{
...
struct device_node *node = pdev→dev.of_node;
struct omap_i2c_dev *omap;
...
irq = platform_get_irq(pdev, 0);

omap = devm_kzalloc(&pdev->dev, sizeof(struct omap_i2c_dev), GFP_KERNEL);
...
mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
omap->base = devm_ioremap_resource(&pdev->dev, mem);
u32 freq = 100000; /* default to 100000 Hz */

of_property_read_u32(node, "clock-frequency", &freq);

return 0;
}
From drivers/i2c/busses/i2c-omap.c
Device tree bindings

Device tree bindings provide a specification of properties that
a driver expects in a DT

Bindings are available in
Documentation/devicetree/bindings in kernel
sources.

To know how to set device properties, look for a binding for
the same compatible string:
$ git grep “ti,omap4-i2c” Documentation/devicetree/bindings/
Another bus example: I2C
I2C drivers: probe() function
static int mma7660_probe(struct i2c_client *client,
const struct i2c_device_id *id) device structure for the i2c bus
{
int ret; needed to communicate with the device
struct iio_dev *indio_dev;
struct mma7660_data *data; Framework (here iio) structure for each device
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev) { Per device structure. Used by the driver
dev_err(&client->dev, "iio allocation failed!\n");
return -ENOMEM; to store references to bus and framework structures,
} plus its own data (locks, wait queues, etc.)
data = iio_priv(indio_dev); Allocation of the framework structure. This structure
data->client = client;
i2c_set_clientdata(client, indio_dev);
also contains the per device structure (data)
mutex_init(&data->lock);
data->mode = MMA7660_MODE_STANDBY; Reference to the bus structure stored in the
framework structure.
indio_dev->dev.parent = &client->dev;
indio_dev->info = &mma7660_info;
indio_dev->name = MMA7660_DRIVER_NAME; Reference to the framework structure stored in the
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = mma7660_channels;
bus structure.
indio_dev->num_channels = ARRAY_SIZE(mma7660_channels);
Enabling the device (i2c reading and writing)
ret = mma7660_set_mode(data, MMA7660_MODE_ACTIVE);
if (ret < 0) Register a new framework device when everything is
return ret;
ready (device now accessible in user-space)
ret = iio_device_register(indio_dev);
if (ret < 0) {
dev_err(&client->dev, "device_register failed\n"); Disabling the device in case of failure
mma7660_set_mode(data, MMA7660_MODE_STANDBY);
}

}
return ret; From drivers/iio/accel/mma7660.c
I2C drivers: remove() function

sstatic int mma7660_remove(struct i2c_client *client) Same i2c device structure as in probe()
{
struct iio_dev *indio_dev = i2c_get_clientdata(client);
Get back the framework structure.
iio_device_unregister(indio_dev); Needed to unregister the framework
device from the system
return mma7660_set_mode(iio_priv(indio_dev),
MMA7660_MODE_STANDBY);
Unregister the framework
}
device from the system

Now that user-space can’t access


the device any more, disable the
device.

From drivers/iio/accel/mma7660.c
I2C driver registration
static const struct i2c_device_id mma7660_i2c_id[] = {
{"mma7660", 0},
Matching by name (mandatory for I2C)
{}
};
MODULE_DEVICE_TABLE(i2c, mma7660_i2c_id);

static const struct of_device_id mma7660_of_match[] = { Matching by compatible property (for DT)
{ .compatible = "fsl,mma7660" },
{ }
};
MODULE_DEVICE_TABLE(of, mma7660_of_match);

static const struct acpi_device_id mma7660_acpi_id[] = { Matching by ACPI ID (for ACPI systems - x86)
{"MMA7660", 0},
{}
};

MODULE_DEVICE_TABLE(acpi, mma7660_acpi_id);

static struct i2c_driver mma7660_driver = {


.driver = {
.name = "mma7660",
.pm = MMA7660_PM_OPS,
.of_match_table = mma7660_of_match,
.acpi_match_table = ACPI_PTR(mma7660_acpi_id),
},
.probe = mma7660_probe,
.remove = mma7660_remove,
.id_table = mma7660_i2c_id,
};

module_i2c_driver(mma7660_driver);

From drivers/iio/accel/mma7660.c
Driver development advise

Look for code for devices similar to yours

Read the code.
You can use Elixir (https://elixir.bootlin.com/)

Always read code from the bottom up. You see
the big picture first, and then progressively how
the details are implemented.
Further reading

Bootlin’s kernel and driver
development training materials for full
details
https://bootlin.com/training/kernel/

Device Tree for Dummies presentation
Thomas Petazzoni (Apr. 2014)
http://j.mp/1jQU6NR
Questions?
Thank you!

You might also like