Linux Device Driver Course
Linux Device Driver Course
com
Copyright notice
Linux device driver programming power point
presentation by BHARATI SOFTWWARE is licensed under CC BY-SA 4.0
To view a copy of this license, visit
https://creativecommons.org/licenses/by-sa/4.0
Links
• For the full video course please visit
• https://www.udemy.com/course/linux-device-driver-programming-using-
beaglebone-black/
• Course repository
• https://github.com/niekiran/linux-device-driver-1
• Explore all Fastbit EBA courses
• http://fastbitlab.com/course1/
• For suggestions and feedback
• [email protected]
Social media
• Join our Facebook private group for technical discussion
• https://www.facebook.com/groups/fastbiteba/
• Linkedin
• https://www.linkedin.com/company/fastbiteba/
• Facebook
• https://www.facebook.com/fastbiteba/
• YouTube
• https://www.youtube.com/channel/UCa1REBV9hyrzGp2mjJCagBg
• Twitter
• https://twitter.com/fastbiteba
Host (PC running Ubuntu 18.04 OS 32/64bit) Target Beaglebone Black Rev A5
Workspace setup
• You may follow the below folder setup to work with this course
Partition 1 Partition 2
BOOT ROOTFS
Tool-chain download
32bit OS
64bit OS
Steps:
1.Go to you home directory
2.Open .bashrc using vim or gedit text editor
3.Copy the below export command with path information to .bashrc file
export PATH=$PATH:<path_to_tool_chain_binaries>
4. Save and close
or simply do
echo “export PATH=$PATH:<path_to_tool_chain_binaries>” > ~/.bashrc
Target preparation
Board components
Sitara AM3358BZCZ100
Beaglebone Black A5
Powering
Important Note : RXD of the cable must be connected to TXD of the BBB serial header (pin 5)
TXD of the cable must be connected to RXD of the BBB serial header(pin 4)
GND of the cable must be connected to GND of the BBB serial header(pin 1)
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
1. MMC1(eMMC) 1. SPI0
BOOT sequence
BOOT sequence
3. UART0 3. USB0
4. USB0 4. UART0
(S2)
• Reset button: Pressing this button resets the board. Note that the boot sequence
is not affected by the reset action.
• Boot button: you can use this button to change the boot sequence during power-
up of the board.
AM335x SOC
DDR interface
1. MMC1(eMMC) 1. SPI0
BOOT sequence
BOOT sequence
3. UART0 3. USB0
4. USB0 4. UART0
STEP 2:
/*creates a .config file by using default config file given by the vendor */
STEP 4:
/*Kernel source code compilation. This stage creates a kernel image "uImage" also all the
device tree source files will be compiled, and dtbs will be generated */
STEP 6:
/* This step installs all the generated .ko files in the default path of the computer
(/lib/modules/<kernel_ver>) */
If you reboot your machine, again you must run these commands.
So, its better if you create a small script and execute when your machine reboots.
1MB
MBR mmcblk1p1
MLO
U-boot mmcblk1
LKM
Running Linux Kernel
So, when you’re building the kernel, you can either link modules directly into the kernel
or build them as separate modules that can be loaded into the kernel at some other
time.
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
First
kernel
module
Your code
Registration
Module description
Header
Your code
The module initialization function is module-specific and should never be called from
other modules of the kernel. It should not provide any services or functionalities
which may be requested by other modules. Hence it makes sense to make this
function private using ‘static’ though it is optional.
• __exit
__init
• __init and __exit makes sense only for static modules (built-in modules)
• __init is a macro which will be translated into compiler directive, which instructs the
compiler to put the code in .init section of the final ELF of linux kernel image.
• .init section will be freed from the memory by the kernel during boot time once all the
initialization functions get executed.
• Since the built-in driver cannot be unloaded, its init function will not be called again
until the next reboot, that’s why there is no need to keep references to its init function
anymore.
• so using __init macro is a technique, when used with a function, the kernel will free the
code memory of that function after its execution.
• Similarly, you can use __initdata with variables that will be dropped after the
initialization. __initdata, which works similarly to __init but for init variables rather than
functions.
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
__exit
• You know that for built-in modules clean-up function is not required
• So, when you use the __exit macro with a clean-up function, the
kernel build system will exclude those functions during the build
process itself.
.init section
memory will be
Static modules
freed by the kernel
during boot.
int __init m1_init_fun (void) { …. }
m1_init_fun
module1.c m2_init_fun .init
Kernel build
int __init m2_init_fun (void) { …. } system m3_init_fun .text
(Kbuild)
module2.c
• These are the macros used to register your module’s init function and clean-up
function with the kernel.
• Here module_init/module_exit is not a function, but a macro defined in linux/module.h
• For example, module_init() will add its parameter to the init entry point database of the
kernel
• module_exit() will add its parameter to exit entry point database of the kernel
Module description
• MODULE_LICENSE is a macro used by the kernel module to announce its license type .
• If you a load module whose license parameter is non-GPL(General Public License), then kernel
triggers warning of being tainted. Its way of kernel letting the users and developers know its non-
free license based module.
• The developer community may ignore the bug reports you submit after loading the proprietary
licensed module
• The declared module license is also used to decide whether a given module can have access to the
small number of "GPL-only" symbols in the kernel.
• Go to linux/module.h to find out what are the allowed parameters which can be used with this
macro to load the module without tainting the kernel.
MODULE_INFO
MODULE_INFO(name, “string_value");
• MODULE_INFO(board,”Beagle bone”);
You can see the module information by running the below command on the .ko file
arm-linux-gnueabihf-objdump -d -j .modinfo hellowrold.ko
Reference : https://www.kernel.org/doc/Documentation/kbuild/modules.txt
Important note:
• When you are building out of tree(external ) module, you need to have a
complete and precompiled kernel source tree on your system
• The reason is, modules are linked against object files found in the kernel source
tree
• You can not compile your module against one Linux kernel version and load it into
the system, which is running kernel of different version. The module load may not
be successful, and even if it is successful, you will encounter run time issues with
the symbols.
• Thumb rule: “Have a precompiled Linux kernel source tree on your machine and
build your module against that “
• There are two ways to obtain a prebuilt kernel version
• Download kernel from your distributor and build it by yourself
• Install the Linux-headers- of the target Linux kernel
Command syntax
make -C $KDIR M=$PWD [Targets]
Command syntax
make -C $KDIR M=$PWD [Targets]
• modules :The default target for external modules. It has the same functionality
as if no target was specified.
• modules_install: Install the external module(s). The default location is
/lib/modules/<kernel_release>/extra/, but a prefix may be added with
INSTALL_MOD_PATH
• clean : Remove all generated files in the module directory only.
• help:List the available targets for external modules
Here obj-<X> is kbuild variable and ‘X’ takes one of the below values
X = n , Do not compile the module
X= y, Compile the module and link with kernel image
X = m , Compile as dynamically loadable kernel module
obj-m := main.o
The kbuild system will build main.o from main.c and after linking, will result in the
kernel module main.ko.
In-tree building
• You have to add the Linux kernel module inside the Linux kernel
source tree and let the Linux build system builds that.
• If you want to list your kernel module selection in kernel menuconfig,
then create and use a Kconfig file
Ref :- https://www.kernel.org/doc/Documentation/kbuild/kconfig-language.txt
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
linux/Documentation/printk-formats.txt
include/linux/kern_levels.h
The log level will be used by the kernel to understand the priority of the message .
Based on the priority kernel decides whether the message should be presented to
the user immediately, by printing directly on to the console.
The kernel message log level will be compared with the current console log level. If the kernel message log level
is lower than the current console log level, then the message will be directly printed on the current console.
By default console log level will have the value of config item: CONFIG_CONSOLE_LOGLEVEL_DEFAULT
Its default value is set to 7. You can change it via kernel menuconfig or running commands.
At run time you can change the log level values using the below command
echo 6 > /proc/sys/kernel/printk
printk wrappers
Name Log Alias function
level
KERN_EMERG “0” pr_emerg
KERN_ALERT “1” pr_alert
KERN_CRIT “2” pr_crit printk(KERN_INFO “Hello this is jut for your information\n”);
KERN_ERR “3” pr_err
pr_info(“Hello this is jut for your information\n”);
KERN_WARNING “4” pr_warning
KERN_NOTICE “5” pr_notice
KERN_INFO “6” pr_info
KERN_DEBUG “7” pr_debug (works
only if DEBUG is
defined)
KERN_DEFAULT “”
printk wrappers
Include/linux/printk.h
lib/hexdump.c
Device driver
• The device driver code knows, how to configure the device, sending data to the
device, and it knows how to process requests which originate from the device.
• When the device driver code is loaded into the operating system such as Linux, it
exposes interfaces to the user-space so that the user application can communicate
with the device.
• Without the device driver, the OS/Application will not have a clear picture of how to
deal with a device
Applications
User space
Kernel subsystems
Device drivers
Hardware space
write
User level programs App2 open
cp
read
User space App1 echo
Hardware XYZ
Kernel Space
0xAB
write(fd,0xAB); RTC driver RTC device
echo 0xAB > /dev/rtc
Block drivers
• The device which handles data in chunks or blocks is called a block
device.
• Block drivers are more complicated than char drivers because block
drivers should implement advanced buffering strategies to read and
write to the block devices, and disk caches are involved.
• Examples: mass storage devices such as hard disks, SDMMC, Nand
flash, USB camera, etc
Device files
• Devices are accessed as a file in Unix/Linux systems.
• A device file is a special file or a node which gets populated in /dev
directory during kernel boot time or device/driver hot plug events
• By using device file, user application and drivers communicate with each
other.
• Device files are managed as part of VFS subsystem of the kernel
/dev/xyz Kernel
Device files
User program
module/Driver
Device files
User application
open( “/dev/rtc” ,….);
read(fd,….);
write(fd….);
User space
GNU C library
read(){ Hardware
….. rtc
VFS
}
write()
{
…….
}
Kernel module/Driver
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
User space
/dev/rtc
write()
{
…….
}
Kernel module/Driver
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
class_create();
3. Create device files
device_create();
Creation Deletion
alloc_chrdev_region(); unregister_chrdev_region();
cdev_init(); cdev_del();
cdev_add();
class_create(); class_destroy();
device_create(); device_destroy();
cdev_init() include/linux/cdev.h
cdev_add()
cdev_del()
device_create() include/linux/device.h
class_create()
device_destroy()
class_destroy()
copy_to_user() include/linux/uaccess.h
copy_from_user()
VFS structure definitions inclue/linux/fs.h
first of the
requested range of
output parameter
minor numbers
for first assigned
number
number of minor
numbers required
Example
Example
Name of this device numbers “eeprom”
class_create();
3. Create device files
device_create();
structure to initialize
Example
Include/linux/fs.h
};
struct CarModel CarBMW ={2021,15000,220,1330 }; // C89 method . Order is important
THIS_MODULE
• THIS_MODULE is a macro which resolves in to ‘pointer to a struct
module variable which corresponds to our current module”
• You can find this macro in linux/export.h
Example
class_create();
3. Create device files
device_create();
udev
• So, udev relies on device information being exported to user space
through sysfs
• uevents are generated when device driver takes the help of kernel
APIs to trigger the dynamic creation of device files or when a hot-
pluggable device such as a USB peripheral is plugged into the system
• The driver exports all the information regarding the device such as device file name,
major , minor number to sysfs by calling the function device_create
• udev looks for a file called ‘dev’ in the /sys/class/ tree of sysfs, to determine what the
major and minor number is assigned to a specific device
class_Create:
Create a directory in sysfs : /sys/class/<your_class_name>
device_create :
This function creates a subdirectory under
/sys/class/<your_class_name> with your device name.
This function also populates sysfs entry with dev file which consists of
the major and minor numbers, separated by a : character
/sys/class/your_class_name/your_device_name/dev /dev/your_device_name
/sys/class/your_class_name
dev file contains major and minor
numbers, separated by a : character.
udevd udev scans and reads this file to create a
User space device node for your device under /dev
directory
1 2
uevents
3
Kernel space
class_create(“your_class_name”);
Your driver should add all the info regarding
the device (such as device file name, major ,
device_Create(your_calss_name, device_number, “your_device_name”) minor number) to sysfs by calling the function
device_create
Driver/Kernel module
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Example
Example
Include/linux/fs.h
write()
{
…….
}
pcd driver
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Open
fd = open(“/dev/pcd”,O_RDWR);
Kernel space
Pointer of Inode
VFS associated with
filename Pointer of file object
Kernel space
Struct
struct cdev
file_ops
VFS
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Inode object
• Unix makes a clear distinction between the contents of a file and the information
about a file
• An inode is a VFS data structure(struct inode) that holds general information about a
file.
• Whereas VFS ‘file’ data structure (struct file) tracks interaction on an opened file by
the user process
• Inode contains all the information needed by the filesystem to handle a file.
• Each file has its own inode object, which the filesystem uses to identify the file
• Each inode object is associated with an inode number, which uniquely identifies the
file within the filesystem.
• The inode object is created and stored in memory as and when a new file (regular or
device ) gets created
File object
• Whenever a file is opened a file object is created in the kernel space .
There will one file object for every open of a regular or device file.
• This information exists only in kernel memory during the period when
a process has the file open. The contents of file object is NOT written
back to disks unlike inode.
For more details refer Understanding linux kernel 3rd edition, page 524 “The open( ) System Call “
f_op write
i_cdev read
Open
File object
f_op
cdev Driver
F_ops
Summary
When device file gets created
1) create device file using udev
2) inode object gets created in memory and inode’s i_rdev field is initialized with device number
3) inode object’s i_fop field is set to dummy default file operations (def_chr_fops )
Open method
Pointer of file object
Pointer of Inode
associated with
filename
Return :
0 if open is successful
Negative error code if open fails
Close
close(fd);
Kernel space
VFS
Release method
Release method
• In release method the driver can do reverse operation of what open had
done.
• E.g. if open method brings the device out of low power mode, then release
method may send the device back to the low power mode.
• Basically you should leave the device in its default state, the state which was
before the open call.
• Free any data structures allocated by the open method
• Return 0 on success . Negative error code if any errors
• For example you try to de-initialize the device and the device doesn’t respond
Read
read(fd,buff,20);
Kernel space
VFS
pcd driver
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Read method
Pointer of current file
Pointer of user buffer position from which the
read has to begin
Pointer of file object
Read method
• Read ‘count’ bytes from a device starting at position ‘ f_pos’.
• Update the ‘f_pos’ by adding the number bytes successfully read
• Return number of bytes successfully read
• Return 0 if there is no bytes to read (EOF)
• Return appropriate error code (-ve value) if any error
• A return value less than ‘count’ does not mean that an error has
occurred.
__user macro
• It’s a macro used with user level pointers which tells the developer
not to trust or assume it as a valid pointer to avoid kernel faults.
• Never try to dereference user given pointers directly in kernel level
programming . Instead use dedicated kernel functions such as
copy_to_user and copy_from_user
• GCC doesn’t care whether you use __user macro with user level
pointer or not. This is checked by sparse , a semantic checker tool of
linux kernel to find possible coding faults .
Write
write(fd,buff,20);
Kernel space
VFS
pcd driver
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Write method
Pointer of current file
Pointer of user buffer position from which the
Pointer of file object write has to begin
Write method
• Write ‘count’ bytes into the device starting at position ‘f_pos’.
• Update the ‘f_pos’ by adding the number bytes successfully written
• Return number of bytes successfully written
• Return appropriate error code (-ve value) if any error
llseek
llseek(fd,buff,20);
Kernel space
VFS
pcd driver
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
llseek Origin
Pointer of file object
Offset value
llseek method
• In the llseek method, driver should update the file pointer by using
‘offset’ and ‘whence’ information
• The llseek handler should return , newly updated file position or error
Creation Deletion
alloc_chrdev_region(); unregister_chrdev_region();
cdev_init(); cdev_del();
cdev_add();
class_create(); class_destroy();
device_create(); device_destroy();
User buffer
read
Device memory (memory of the
buffer Copy_to_user() process address
space )
0 1 2 3 4 5 …. 510 511
f_pos
(current file position)
0 1 2 3 4 5 6 7 8 …. 510 511
w w
EOF Scenario
0 1 2 3 4 5 6 7 8 …. 510 511 512
w w
f_pos
(current file position)
Destination Number of
address, bytes to copy
in user space.
Source address,
in kernel space
Source address,
in user space.
Returns 0 on success or number of bytes that could not be copied
If this function returns non zero value , you should assume that
there was a problem during data copy.So return appropriate
error code (-EFAULT)
llseek Origin
Pointer of file object
Offset value
include/uapi/asm-generic/errno-base.h
User buffer
Device memory write (memory of the
buffer Copy_from_user() process address
space )
If whence = SEEK_SET
flip->f_pos = off
If whence = SEEK_CUR
flip->f_pos = flip->f_pos + off
if whence = SEEK_END
flip->f_pos = DEV_MEM_SIZE + off
Exercise
• Modify the previous pseudo character device driver to support
four pseudo character devices.
• Implement open, release, read, write, lseek driver methods to
handle user requests.
Driver Pcdev-1
pcdrv
Pcdev-2
Pcdev-4 Pcdev-3
Driver Pcdev-1
pcdrv
/dev/pcdev-1
Pcdev-2
/dev/pcdev-2
Pcdev-3
Pcdev-4
/dev/pcdev-3
/dev/pcdev-4
Start address
echo “Good morning” > /dev/pcdev-1 echo “Good morning” > /dev/pcdev-3
Kernel space
Driver
pcdrv
Start address
(4)
container_of macro
/include/linux/kernel.h
container_of
• Container_of macro helps you to get the address of the containing
structure by taking an address of its member element .
• As its name indicates it gives you the “container” address of the
member element of a struct
• It takes three arguments – a pointer, type of the container, and
the name of the member the pointer refers to.
Address of the
member element Name of the member
to which ‘ptr’ refers to
Structure type
a b c d
1by
te
Padding
a b c d
1byte
linux/kernel.h
#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
The macro offsetof() returns the offset of the field MEMBER from
the start of the structure TYPE
f_mode
• This is one of the fields of struct file defined in <linux/fs.h>
• you can check this field in your driver to understand access mode
requested from the user space application
• f_mode has bit fields to indicate access modes read or write . So, use
macros FMODE_READ and FMODE_WRITE to decode this field
(defined in <linux/fs.h>)
• Platform bus
• Platform devices
• Platform drivers
Bus
• In computer science, a bus is a collection of electrical wirings which
transfers information (data or address or control ) between devices.
PC Scenario L2
cache
System Memory
Processor D D D
cache Memory R R R
controller A A A
M M M
Bridge
PCI bus
An Embedded platform
MCU/SoC
Bus SRAM
Processor
matrix
FLASH
Temperature
Mic
AHB
RTC sensor Headset
LCD controller
GPIO
BRIDGE
APB
APB
MMC card bus
SDIO USB HUB
Flash drive
connector
ADC WDG
keyboard
AHB
RTC sensor Headset
LCD controller
GPIO
BRIDGE
APB
APB
MMC card bus
SDIO USB HUB
Flash drive
connector
ADC WDG
keyboard
Platform bus
Processor
Platform bus
Dev-1 Dev-1
Dev-2
Embedded
Scenario-STM32
Embedded Scenario-OMAP4460
Embedded Scenario-AM335x
Discovery of devices
• Every device has its configuration data and resources, which need to be reported to the OS,
which is running on the computer system.
• An operating system such as Windows or Linux, running on the computer, can auto-discover
these data. Thus the OS learn about the connected devices automatically (Device
enumeration )
• Enumeration is a process through which the OS can inquire and receive information, such as
the type of the device, the manufacturer, device configuration, and all the devices connected
to a given bus.
• Once the OS gathers information about a device, it can autoload the appropriate driver for
the device. In the PC scenario, buses like PCI and USB support auto enumeration/hotplugging
of devices.
• However, on embedded system platforms, this may not be the case since most peripherals
are connected to the CPU over buses that don’t support auto-discovery or enumeration of
devices. We call them as platform devices.
• All these platform devices which are non-discoverable by nature, but they must be part of the
Linux device model, so the information about these devices must be fed to the Linux kernel
manually either at compile time or at boot time of the kernel.
Device information
1. Memory or I/O mapped base address and range information
2. IRQ number on which the device issues interrupt to the processor
3. Device identification information
4. DMA channel information
5. Device address
6. Pin configuration
7. Power , voltage parameters
8. Other device specific data
https://www.kernel.org/doc/Documentation/devicetree/usage-model.txt
Platform devices
• Devices which are connected to the platform bus are called platform
devices
• A device if its parent bus doesn’t support enumeration of connected
devices then it becomes a platform device
Platform driver
• A driver who is in charge of handling a platform device is called a
platform driver
AM335X
block diagram
/include/linux/platform_device.h
(Deprecated)
Platform Bus
(Linux platform core)
p.dev-2 p.drv-2
p.dev-3 p.drv-3
The Linux platform core implementation
maintains platform device and driver lists.
Whenever you add a new platform device or
driver, this list gets updated and matching
Every bus type has its match function, where mechanism triggers.
the device and driver list will be scanned.
Platform Bus
Device list Driver list
Platform Bus
Points to remember
• Whenever a new device or a new driver is added, the matching
function of the platform bus runs, and if it finds a matching platform
device for a platform driver, the probe function of the matched driver
will get called. Inside the probe function, the driver configures the
detected device.
• Details of the matched platform device will be passed to the probe
function of the matched driver so that driver can extract the platform
data and configure it.
module_platform_driver(__platform_driver);
Include/linux/platform_device.h
Code exercise:
Implementation of pseudo character driver as platform driver
Platform Bus
(Linux platform core)
Pcdev-2
Pcdev-3
Implementation
• Callback to free the device after all references have gone away.
• This should be set by the allocator of the device
TS-A1x
Platform driver must support
TS-B1x
different device ids
TS-C1x
Device tree
Device tree
• Introduction to the device tree
• Device tree structure
• Example of a device node
• properties of device trees
• Device tree overlays
Source :
Documentation/devicetree/usage-model.txt
Device tree
• An operating system uses the Device Tree to discover the topology of
the hardware at runtime, and thereby support a majority of available
hardware without hardcoded information (assuming drivers were
available for all devices)
Device tree
• DT provides a language for decoupling the hardware configuration
from the device driver and board support files of the Linux kernel (or
any other operating system for that matter).
• Using it allows device drivers to become data-driven. To make setup
decisions based on data passed into the kernel instead of on per-
machine hardcoded selections.
• Ideally, a data-driven platform setup should result in less code
duplication and make it easier to support a wide range of hardware
with a single kernel image.
Source :
Documentation/devicetree/usage-model.txt
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Why DT is used ?
Linux uses DT for,
• Platform identification
• Device population:
• The kernel parses the device tree data and generates the
required software data structure, which will be used by the
kernel code.
More reading
• https://elinux.org/Device_Tree_What_It_Is
• https://www.kernel.org/doc/Documentation/devicetree/usage-
model.txt
• For example, when you design a new board, which is slightly different from another
reference board, then you can reuse the device tree file of the reference board and
only add that information which is new in your custom board.
3G/4G
Wifi keypad LCD+tsc
modem
Connectors
USB
LED0 HOST
Ethernet JTAG
LED1
LED2
SOC
DDR3
USER BTN
DC
power
Connectors
Ext SD CAN
AV
memory connector TXRX
An Embedded Board
3G/4G
Wifi keypad LCD+tsc
modem
Connectors
USB
LED0 HOST
Ethernet JTAG
LED1
LED2
SOC
DDR3
USER BTN
DC
power
Connectors
Ext SD CAN
AV
memory connector TXRX
An Embedded Board
Includes
AM335x dtsi
/ {
Node-1 {
a-string-property = "A string";
A-32-bit-integer-property = <800>;
string-list-property = “1ststring”,2ndstring”;
Child-Node-1 {
byte-array-property = <0x30 0x20 0xFE 0x10>;
};
};
Node-2 {
A-Boolean-property;
Child-Node-1 {
};
Child-Node-2 {
};
};
};
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Root node
• The device tree has a single root node of which all other device nodes
are descendants. The full path to the root node is /.
• All device trees shall have a root node, and the following nodes shall
be present at the root of all device trees:
• One /CPUs node
• At least one /memory node
Summary
• Remember that you most probably be writing device tree addons or
overlays for your board-related changes but not for entire soc.
• The soc specific device tree will be given by the vendor in the form of
device tree inclusion file (.dtsi ) and you just need to include that in
your board-level device tree
• Follow modulatory approach while writing device tree
AM335x dtsi
Node name
0xFFFF_FFFF
0x4802_a000 I2c-1_register_0
I2C-0 Register set in the memory map Memory map of the SOC
0x44e0_bffC I2c-0_register_n
0x0000_0000
32bits
I2C-1
AM33xx SOC
eeprom
I2c addr = 0x50
32KB EEPROM is provided on I2C0
that holds the board information
/ {
Parent
i2c0: i2c@44e0b000 {
I2c addr = 0x24
};
Child-2 eeprom {
eeprom
};
I2c addr = 0x50
32KB EEPROM is provided on
I2C0 that holds the board
};
information
};
am33xx.dtsi
am335x-bone-common.dtsi BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Key points
• Node name is in the format node-name@unit-address
• This combination differentiates the node from other nodes at the
same level in the tree
• To write the “node-name” alphabet (stick to lower case), and number
combinations are allowed. So choose an appropriate name , it would
be good if it represents the general class of a device like serial, i2c,
led, etc
• The unit-address must match the first address specified in the reg
property of the node. If the node has no reg property, the @unit-
address must be omitted
PCD example
Driver Pcdev-1
pcdrv
Pcdev-2
Pcdev-3
Pcdev-4 Size
device-serial-num
Perm
Properties
Properties
What is a property ?
Property name(key) Value
A single property
Properties
'compatible' property
Property :compatible
compatible = <string-list>
property name(Standard ) Value type
e.g. :
compatible = “string1” , “string2” , “string3”;
• Compatible property of a device node is used by the Linux kernel to match
and load an appropriate driver to handle that device node (driver
selection)
• The string list provided by the compatible property is matched against the
string list supported by the driver. If the kernel finds any match, the driver
will be loaded, and probe function is invoked
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
};
It claims that, this is an
exact compatible name
of the board for kernel to support it
It claims that, it is also compatible
It claims that, it is also compatible with
with kernel which supports only
kernel which supports bone devices
am33xx soc based board, if kernel
The machine identification fails when based on am335x, if the kernel doesn’t
doesn’t support previous options.
none of these string values matches with support previous exact model.
kernel supported compatible machine
identification string-list.
Note:
• Compatible strings and properties are first defined by the client
program (OS , drivers ) then shared with DT writer
Driver
DT implementation
implementation
https://elinux.org/Device_Tree_Linux#Linux_vs_ePAPR_Version_1.1
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
pcdev properties
org,size = <512> ; //this is a non standard property and represents integer data ;
org,device-serial-num = “PCDEV1ABC123” ; //this is a non standard property and
represents string data
org,perm = <0x11> ; //this is a non standard property and represents integer data
struct platform_device
struct platform_device
struct platform_device
struct platform_device
platform_device
Struct device
Struct device_node
if(of_property_read_u32(np,"fb,size",&size)){
dev_info(dev,"Missing size property\n");
return -EINVAL;
}
i2c@3000 {
#address-cells = <1>;
#size-cells = <0>;
cell-index = <0>;
we can make the following observations about this example node:
compatible = "fsl-i2c";
• The I2C controller is located at offset 0x3000 from its parent.
reg = <0x3000 0x100>;
• The driver for the I2C controller is fsl-i2c.
interrupts = <43 2>;
• The first child is named dtt, at offset 0x48 from its parent; the
interrupt-parent = <&mpic>;
driver is national lm75.
dfsrr;
• The second child is named rtc, at offset 0x68 from its parent; the
dtt@48 {
driver is Dallas ds1337.
compatible = "national,lm75";
reg = <0x48>;
};
rtc@68 {
compatible = "dallas,ds1337";
reg = <0x68>;
};
};
Platform_device
i2c@3000 {
#address-cells = <1>;
#size-cells = <0>; Device
cell-index = <0>;
compatible = "fsl-i2c";
reg = <0x3000 0x100>; Associated Device node(of_node)
interrupts = <43 2>; device tree
interrupt-parent = <&mpic>; node
child
dfsrr;
Associated
dtt@48 {
device tree
compatible = "national,lm75"; Device_node
node
reg = <0x48>;
};
rtc@68 { Sibling
Associated
compatible = "dallas,ds1337";
device tree
reg = <0x68>; Device_node
node
};
};
Device node
struct device_node {
const char *name;
phandle phandle;
const char *full_name;
struct fwnode_handle fwnode;
structproperty *properties;
structproperty *deadprops;/* removed properties */
structdevice_node *parent;
structdevice_node *child;
structdevice_node *sibling;
#if defined(CONFIG_OF_KOBJ)
structkobject kobj;
#endif
unsigned long _flags;
void*data;
#if defined(CONFIG_SPARC)
unsigned int unique_id;
struct of_irq_controller *irq_trans;
#endif BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
};
www.fastbitlab.com
Property
struct property {
Char *name;
Int length;
Void *value;
struct property *next;
#if defined(CONFIG_OF_DYNAMIC) ||
defined(CONFIG_SPARC)
unsigned long _flags;
#endif
#if defined(CONFIG_OF_PROMTREE)
unsigned int unique_id;
#endif
#if defined(CONFIG_OF_KOBJ)
struct bin_attribute attr;
#endif
};
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
include/linux/of_device.h
Include/linux/of.h
am335x-boneblack.dtb
( This is board specific. This explains the
hardware topology of the board )
Two ways you can include the device nodes for the cape device in the
main dtb
1.Edit the main dtb itself (not recommended )
2.Overlay( a Patch which overlays the main dtb) (recommended )
Uses of overlays
1)To support and manage hardware configuration (properties, nodes, pin
configurations ) of various capes of the board
2)To alter the properties of already existing device nodes of the main dtb
3)Overlays approach maintains modularity and makes capes management easier
+ =
DT Overlay exercise
Write a device tree overlay to disable/modify pcdev device nodes from
the main dts
STEPS
1. Create a device tree overlay file and add fragments to modify the
target device nodes
2. Compile the device tree overlay file to generate .dtbo file (Device
tree overlay binary)
3. Make u-boot to load the .dtbo file during board start-up
Overlay compilation
• Make sure that device tree compiler(dtc) is installed on your system
• Run the below command to generate .dtbo from .dts file
dtc -O dtb -o <output-file-name> -I <input-file-name>
return = NULL if allocation fails, on success virtual address of the first page allocated
include/linux/slab.h
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
/*
*%GFP_KERNEL : Allocate normal kernel ram. May sleep.
*%GFP_NOWAIT : Allocation will not sleep.
*%GFP_ATOMIC Allocation will not sleep. May use emergency pools.
*%GFP_HIGHUSER Allocate memory from high memory on behalf of user.
*
* Also it is possible to set different flags by OR'ing
* in one or more of the following additional @flags:
*
* %__GFP_HIGH This allocation has high priority and may use emergency pools.
* %__GFP_NOFAIL Indicate that this allocation is in no way allowed to fail
*(think twice before using).
* %__GFP_NORETRY If memory is not immediately available, then give up at once.
* %__GFP_NOWARN If allocation fails, don't issue any warnings.
* %__GFP_RETRY_MAYFAIL Try really hard to succeed the allocation but fail
eventually.
*/
include/linux/slab.h
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Usage
struct bar *k;
k = kmalloc(sizeof(*k), GFP_KERNEL);
if (!k)
return -ENOMEM;
kfree
/**
* kfree - free previously allocated memory
* @objp: pointer returned by kmalloc.
*
* If @objp is NULL, no operation is performed.
*
* Don't free memory not originally allocated by kmalloc()
* or you will run into trouble.
*/
void kfree(const void *objp);
bar_processing_fun(pbar); }
kzfree
/**
* kzfree - like kfree but zero memory
* @p: object to free memory of
*
* The memory of the object @p points to is zeroed before freed.
* If @p is %NULL, kzfree() does nothing.
*
* Note: this function zeroes the whole allocated buffer which can be a good
* deal bigger than the requested buffer size passed to kmalloc(). So be
* careful when using this function in performance sensitive code.
*/
void kzfree(const void *p);
/**
* krealloc - reallocate memory. The contents will remain unchanged.
* @p: object to reallocate memory for.
* @new_size: how many bytes of memory are required.
* @flags: the type of memory to allocate.
*
* The contents of the object pointed to are preserved up to the
* lesser of the new and old sizes. If @p is %NULL, krealloc()
* behaves exactly like kmalloc(). If @new_size is 0 and @p is not a
* %NULL pointer, the object pointed to is freed.
*
* Return: pointer to the allocated memory or %NULL in case of error
*/
(platform device)
Struct device
Memory is allocated
on behalf of this
device
kmalloc(); devm_kmalloc();
Programmer must Programmers using kfree() is not required.
free the memory Kernel will take care of freeing memory
using kfree(); when the “device” or managing “driver”
gets removed from the system
Examples
Kmalloc() devm_kmalloc()
Kfree()
gpiod_get() devm_gpiod_get()
gpiod_put()
request_irq() devm_request_irq()
free_irq()
https://www.kernel.org/doc/Documentation/driver-model/devres.txt
Refer to this link to check all resource managed kernel apis
Probe function
remove function
Example
Consider the case of a platform device
CPU
Platform bus
Platform
device It’s a device and it’s device type is “ platform device”
(ADC) because it is hanging on platform bus type
Include/linux/device.h
Struct platform_device
Subsystem specific
information
struct device
Device specific
information
parent bus_type
Struct device
device_driver
driver_data platform_data
Example
Consider the case of a platform device .
CPU
I2C Bus
I2C client
device It’s a device and it’s device type is “ i2c client”
(RTC) because it is hanging on i2c bus type
Struct i2c_client
struct device
Device specific
information
CPU
Platform bus
I2C Bus
I2C client
device It’s a device and it’s device type is “ i2c client”
Struct i2c_client
(RTC) because it is hanging on i2c bus type
Off chip device
Each driver of the system is represented in the Linux device driver model as an object of struct device_driver
Kobject
• Kobject stands for kernel object which is represented by struct kobject
• Kobjects are a fundamental building block of Linux device and driver
hierarchy
• Kobjects are used to represent the ‘containers’ in the sysfs virtual
filesystem
• Kobjects are also used for reference counting of the ‘containers’.
• It has got its name, type, and parent pointer to weave the Linux device and
driver hierarchy
• Using kobjects you can add attributes to the container, which can be
viewed/altered by the user space.
• The sysfs filesystem gets populated because of kobjects, sysfs is a user
space representation of the kobjects hierarchy inside the kernel
Include/kobject.h
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent; struct kref {
struct kset *kset; refcount_t refcount;
struct kobj_type *ktype; };
/* sysfs directory entry */
struct kernfs_node *sd; Kobject reference counter
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent;
struct kset *kset;
struct kobj_type *ktype;
/* sysfs directory entry */
struct kernfs_node *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
Device type
Struct platfrom_device
struct device
Container
Embedded kobject
kobj
Reference counter
kref
Kobject type
• Type of a kobject is determined based on, type of the container in
which the kobject is embedded
Struct kobj_type
struct device
This structure is used to define the
default behavior(e.g. attributes ) for a
kobject group of kobjects of same container
ktype type
Struct kobj_type
• struct kobj_type object or simply ktype object is an object which
defines the behavior for the container object.
• Behaviors are manifested in terms of attributes and file operation
methods that handle those attributes.
• The ktype also controls what happens to the kobject when it is
created and destroyed.
• Every structure that embeds a kobject needs a corresponding ktype.
kype
struct kobject {
const char *name;
struct list_head entry;
struct kobject *parent; If you want to assign a type to your kobject , then you have to
struct kset *kset; create an object of type ‘struct kobj_type’ and initialize the
struct kobj_type *ktype; ‘ktype’ field of the kobject.
/* sysfs directory entry */
struct kernfs_node *sd;
struct kref kref;
unsigned int state_initialized:1;
unsigned int state_in_sysfs:1;
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
Kset
• Kset as its name indicates it’s a set of kobjects of same type and
belongs to a specific subsystem.
• Basically kset is a higher-level structure which ‘collects’ all lower level
kobjects which belong to same type
Device type
Struct platfrom_device
struct device
Container
Embedded kobject
kobj
Reference counter
kref
kobject
Different bus_types
(bus subsystems)
kobjects
kset
kobject
Attribute
Attribute
Attribute
kobject
Attribute
kobject
Attribute
kobject
Attribute
kobject
Attribute
What is sysfs ?
Sysfs is a virtual in-memory file system which provides
1. Representation of the kobject hierarchy of the kernel.
2. The complete topology of the devices and drivers of the system in
terms of directories and attributes.
3. Attributes to help user space application to interact with devices
and drivers
4. Standard methods to access devices using ‘classes’
Using sysfs
• sysfs is always compiled in if CONFIG_SYSFS is defined
• You can access it by doing mount -t sysfs sysfs /sys
• Documentation :
https://www.kernel.org/doc/Documentation/filesystems/sysfs.txt
kobject
Attribute
kobject kobject
Attribute
Attribute
Attribute
Attribute
Attribute
Attribute
Attribute
Attribute
Attribute
Include/linux/sysfs.h
Mode of an attribute
• S_IRUGO – world-read-only
• S_IRUSR – owner read-only
• S_IRUGO | S_IWUSR : world-read and only owner write
include/linux/sysfs.h
include/linux/device.h
Usage
static struct device_attribute dev_attr_bar = {
.attr = {
.name = “bar",
.mode = S_IWUSR | S_IRUGO,
},
.show = show_bar,
.store = store_bar,
};
is equivalent to doing:
Show method
Prototype:
ssize_t (*show)(struct device *dev, struct device_attribute *attr , char *buf);
This function is invoked when user space program tries to read the value of the attribute.
Here you must copy the value of the attribute in to the ‘buf’ pointer.
Show method
Store method
ssize_t (*store)(struct device *dev, struct device_attribute *attr, \
const char *buf, size_t count);
This is invoked when user wants to modify the value of the sysfs file .
In this method , ‘buf’ points to the user data.
'count' parameter is the amount of user data being passed in.
The maximum amount of data which the ‘buf’ pointer can carry is limited PAGE_SIZE
The data carried by the ‘buf’ is NULL terminated .
store() should return the number of bytes used from the buffer.
If the entire buffer has been used, just return the count argument.
Attribute grouping
• Instead of calling sysfs_create_file to create a sysfs file for every
attribute. You can finish it off in one call using attribute grouping
Attribute grouping
Include/linux/sysfs.h
lib/string.c
Device unregister
What's an GPIO ?
GPIO2 GPIO3
GPIO0 GPIO1
0 1 30 31 0 1 30 31
0 1 30 31 0 1 30 31
AM335x GPIO
controllers
Memory map
Region Name Start Address (hex) End Address (hex) Size Description
GPIO0 0x44E0_7000 0x44E0_7FFF 4KB GPIO0 Registers
GPIO1 0x4804_C000 0x4804_CFFF 4KB GPIO1 Registers
GPIO2 0x481A_C000 0x481A_CFFF 4KB GPIO2 Registers
GPIO3 0x481A_E000 0x481A_EFFF 4KB GPIO3 Registers
GPIOx register addresses
Pin multiplexing
Pin configuration register
Mode0
I2C0_CLK
Mode1
MMC_DATA
Mode2 pin of the SOC
UART_TX
Functions Mode3
CAN_TX
Mode4
I2C0_DATA
Mode5
LCD_DATA
Mode6
GPIO
Reference : https://beagleboard.org/Support/bone101
BHARATI SOFTWARE , CC BY-SA 4.0 , 2020
www.fastbitlab.com
Kernel space
Consumers
Keypad
Foo driver
driver
gpio_chip
drivers/gpio/gpio-omap.c gpio controller driver
producer
Linux GPIO subsystem
Keypad driver
GPIO controller
GPIO0
0 to 31 pins
Consumer dt node
Key-pad dt node
7pins
Consumer driver
consumer flags
gpio flags
include/dt-bindings/gpio/gpio.h
Acquire
struct gpio_desc* gpiod_get(struct device *dev, const char *con_id, \
enum gpiod_flags flags)
Dispose
void gpiod_put(struct gpio_desc *desc)
Include/linux/consumer.h
GPIO manipulations
• What you do with GPIO lines?
• Configure its direction (input or output )
• Change its output state to 0 or 1
• Read input state
• Configure output type (push-pull/open-drain)
• Enable/Disable pull-up/pull-down resistors
void gpiod_set_value(struct gpio_desc *desc, int value); Set the logical value of the GPIO, i.e. taking its
ACTIVE_LOW, OPEN_DRAIN and OPEN_SOURCE flags
into account.
void gpiod_set_raw_value(struct gpio_desc *desc, int Set the raw value of the GPIO, i.e. the value of its
value); physical line without regard for its ACTIVE_LOW
status.
int gpiod_set_debounce(struct gpio_desc *desc, unsigned sets debounce time for a GPIO
debounce);
int gpiod_is_active_low(const struct gpio_desc *desc); test whether a GPIO is active-low or not
Returns 1 if the GPIO is active-low, 0 otherwise
Exercise
Write a GPIO sysfs driver.
The goal of this exercise is to handle GPIOs of the hardware through Sysfs interface
gpio_1 {
label = "gpio1.21"; /* optional */
bone-gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>; /* mandatory */
};
gpio_2 {
label = "gpio1.22";
bone-gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>;
};
AM335x SOC
a pin
The functionality of a pin depends
on its mode configured in
Multi purpose pin
mode/pad configuration register.
(modes) 0 1 2 3 4 5 6 7
TIMER MMC_D
I2C1_SDA GPIO
Pinctrl-single.c
Vendor given pin
Generic device tree
controller driver
based pinctrl driver
drivers/pinctrl/
pinctrl-single.c Pin controller driver
What is a pin-controller ?
• Hardware modules that control pin multiplexing and configuration
parameters such as pull-up/down, tri-state, drive-strength are
designated as pin controllers.
• Each pin controller must be represented as a node in the device tree,
just like any other hardware module node.
• For am335x, the pad config registers are called as pin controllers
am33xx-l4.dtsi
I2C2_SDA
Pin controller hardware
(pad config registers) I2C2_SCL EEPROM
AM335X
AM335X
GPIO
pinctrl-single,pins
pinctrl-single,pins property
The pin configuration nodes for pinctrl-single are specified as pinctrl
register offset and value pairs using pinctrl-single,pins.
Where 0xdc is the offset from the pinctrl register base address for the
device pinctrl register, and 0x118 contains the desired value of the
pinctrl register. See the device example and static board pins example
below for more information.
Reference :
Documentation/devicetree/bindings/pinctrl/pinctrl-single.txt
I2C2_SDA
Pin controller hardware
(pad config registers) I2C2_SCL
EEPROM
AM335X
AM335X
GPIO
Here, a pin controller has 2 nodes. The first one configures the pins for i2c
functionality and the second one configures the same pins for gpio input
functionality .
The client device will use the first one during normal operation , and when
driver decides to put device to sleep, it will use the second one to
reconfigure the pins to stop leakage of current thus achieving low power
Include/bt-bindings/pinctrl/omap.h
include/bt-bindings/pinctrl/omap.h
Pad configuration
register offset
Include/bt-bindings/pinctrl/am33xx.h
Exercise
Interfacing 16x2 LCD to BBB and testing with gpio-sysfs driver by
writing an LCD application
7
BBB 16x2 Char LCD
lcd_app
User space open read/write
Components required
• 16x2 Character LCD (1602A HD44780 LCD)
• 10K potentiometer
• Breadboard
• Connecting wires
• Beaglebone black
GPIOs of BBB
P9
1
7
P8
45
46
43
44
41
42
39
Note : 4 bit data mode is used. Data pin 0 to Data pin 3 are unused
P9
1 Gnd
7 5V
gnd
Wiper
10K Potentiometer
Power
Steps
1. Connect LCD to BBB
2. Add required gpio entries to the gpio-sysfs device tree node
3. Recompile the dts file and make BBB boots with modified dtb
4. Load the gpio-sysfs driver
5. Make sure that all required gpio devices are formed under
/sys/class/bone_gpios
6. Download the lcd application files attached with this video.
lcd_app.c, lcd.c, gpio.c
7. Cross compile the lcd application and test it on the target
sysfs gpio attributes (direction , value ) (GPIO sysfs entries with attributes)
LCD commands
• Send commands to initialize and control the operation of the LCD
• Commands are 8bits long (a byte )
LCD commands
1. Function set
2. Display on/off, cursor on/off and blink control
3. Entry mode set
4. LCD clear display
5. Cursor return home
6. Set co-ordinates
7. Display right/left shift
8. Cursor on/off, blink on/off
9. Address counter read/write
Sending command/data
• A command or data byte is of 8bits (1byte)
• You can send all 8 bits in one go over 8 data lines, or you can split into
2 data transmissions of 4 bits each.
• For 4-bit data transmission, you only need 4 data lines connected
between LCD and MCU
• For 4-bit data transmission, you must use data lines D4 ,D5, D6,D7
Sending a command
1.Create the command code
2.Make RS pin low
3.Make RnW pin low
4.First send higher nibble(4-bits) of the command code to data lines
5.Make LCD enable pin high to low ( when LCD detects high to low transition on enable
pin it reads the data from the data lines )
6.Next send the lower nibble of the command code to data lines
7.Make LCD enable pin high to low ( when LCD detects high to low transition on enable
pin it reads the data from the data lines )
8. Wait for the instruction execution time before sending the next command or confirm
the LCD is not busy by reading the busy flag status on D7 pin .
1.Make RS high
2.Make RnW low
3.First send higher nibble of the data to data lines
4.Make LCD enable pin high to low ( when LCD detects high to low
transition on enable pin it reads the data from the data lines )
5.Next send the lower nibble of the data to data lines
6.Make LCD enable pin high to low ( when LCD detects high to low
transition on enable pin it reads the data from the data lines )
D4
D5
MCU LCD
D6
D7
7 6 5 4
0x28 0 0 1 0
Higher nibble
D4
D5
MCU LCD
D6
D7
3 2 1 0
0x28 1 0 0 0
Lower nibble
D4
D5
MCU LCD
D6
D7