0% found this document useful (0 votes)
25 views

Chapter 4

Uploaded by

duyhuy362003
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
25 views

Chapter 4

Uploaded by

duyhuy362003
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 20

4

Character Drivers
Typically, an operating system is designed to hide the underlying hardware details from the
user application. Applications do, however, require the ability to access data that is captured by
hardware peripherals, as well as the ability to drive peripherals with output. Since the peripheral
registers are accessible only by the Linux kernel, only the kernel is able to collect data streams as
they are captured by these peripherals.
Linux requires a mechanism to transfer data from kernel to user space. This transfer of data is
handled via device nodes, which are also known as virtual files. Device nodes exist within the
root filesystem, though they are not true files. When a user reads from a device node, the kernel
copies the data stream captured by the underlying driver into the application memory space.
When a user writes to a device node, the kernel copies the data stream provided by the application
into the data buffers of the driver, which are eventually output via the underlying hardware. These
virtual files can be "opened" and "read from" or "written to" by the user application using standard
system calls.
Each device has a unique driver that handles requests from user applications that are eventually
passed to the core. Linux supports three types of devices: character devices, block devices and
network devices. While the concept is the same, the difference in the drivers for each of these
devices is the manner in which the files are "opened" and "read from" or "written to". Character
devices are the most common devices, which are read and written directly without buffering,
for example, keyboards, monitors, printers and serial ports. Block devices can only be written to
and read from in multiples of the block size, typically 512 or 1024 bytes. They may be randomly
accessed, i.e., any block can be read or written no matter where it is on the device. A classic
example of a block device is a hard disk drive. Network devices are accessed via the BSD socket
interface and the networking subsystems.
Character devices are identified by a c in the first column of a listing, and block devices are
identified by a b. The access permissions, owner and group of the device are provided for each
device.

[ 69 ]
Character Drivers Chapter 4

From the point of view of an application, a character device is essentially a file. A process only
knows a /dev file path. The process opens the file by using the open() system call and performs
standard file operations like read() and write().
In order to achieve this, a character driver must implement the operations described in the file_
operations structure (declared in include/linux/fs.h in the kernel source tree) and register them. In
the file_operations structure shown below, you can see some of the most common operations for a
character driver:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
};

The Linux filesystem layer will ensure that these operations are called when a user space
application makes the corresponding system call (on the kernel side, the driver implements and
registers the callback operation).

[ 70 ]
Chapter 4 Character Drivers

The kernel driver will use the specific functions copy_from_user() and copy_to_user() to exchange
data with user space, as shown in the previous figure.
Both the read() and write() methods return a negative value if an error occurs. A return value
greater than or equal to 0, instead, tells the calling program how many bytes have been
successfully transferred. If some data is transferred correctly and then an error happens, the return
value must be the count of bytes successfully transferred, and the error does not get reported until
the next time the function is called. Implementing this convention requires, of course, that your
driver remember that the error has occurred so that it can return the error status in the future.
The return value for read() is interpreted by the calling application program:
1. If the value equals the count argument passed to the read system call, the requested
number of bytes has been transferred. This is the optimal case.
2. If the value is positive, but smaller than the count, then only part of the data has been
transferred. This may happen for a number of reasons, depending on the device. Most
often, the application program retries the read. For instance, if you read using the fread()
function, the library function reissues the system call until completion of the requested
data transfer. If the value is 0, end-of-file was reached.
3. A negative value means there was an error. The value specifies what the error was,
according to <linux/errno.h>. Typical values returned on error include -EINTR (interrupted
system call) or -EFAULT (bad address).
In Linux, every device is identified by two numbers: a major number and a minor number. These
numbers can be seen by invoking ls -l /dev on the host PC. Every device driver registers its major
number with the kernel and is completely responsible for managing its minor numbers. When
accessing a device file, the major number selects which device driver is being called to perform
the input/output operation. The major number is used by the kernel to identify the correct device
driver when the device is accessed. The role of the minor number is device dependent and is
handled internally within the driver. For instance, the Raspberry Pi has several hardware UART
ports. The same driver can be used to control all the UARTs, but each physical UART needs its
own device node, so the device nodes for these UARTs will all have the same major number, but
will have unique minor numbers.

LAB 4.1: "helloworld character" module


Linux systems in general traditionally used a static device creation method, whereby a great
number of device nodes were created under /dev (sometimes literally thousands of nodes),
regardless of whether or not the corresponding hardware devices actually existed. This was
typically done via a MAKEDEV script, which contains a number of calls to the mknod program

[ 71 ]
Character Drivers Chapter 4

with the relevant major and minor device numbers for every possible device that might exist in the
world.
This is not the right approach to create device nodes nowadays, as you have to create a block or
character device file entry manually and associate it with the device, as shown in the Raspberry
Pi´s terminal command line below:
root@raspberrypi:/home/pi# mknod /dev/mydev c 202 108

Despite all this, you will develop your next driver using this static method, purely for educational
purposes, and you will see in the few next drivers a better way to create the device nodes by using
devtmpfs and the miscellaneous framework.
In this kernel module lab, you will interact with user space through an ioctl_test user application. You
will use open() and ioctl() system calls in your application, and you will write its corresponding driver´s
callback operations on the kernel side, providing a communication between user and kernel space.
In the first lab, you saw what a basic driver looks like. This driver didn’t do much except printing some
text during installation and removal. In the following lab, you will expand this driver to create a device
with a major and minor number. You will also create a user application to interact with the driver.
Finally, you will handle file operations in the driver to service requests from user space.
In the kernel, a character-type device is represented by struct cdev, a structure used to register it in the
system.

Registration and unregistration of character devices


The registration/unregistration of a character device is made by specifying the major and minor.
The dev_t type is used to keep the identifiers of a device (both major and minor) and can be
obtained by using the MKDEV macro.
For the static assignment and unallocation of device identifiers, the register_chrdev_region() and
unregister_chrdev_region() functions are used. The first device identifier is obtained by using the
MKDEV macro.
int register_chrdev_region(dev_t first, unsigned int count, char *name);
void unregister_chrdev_region(dev_t first, unsigned int count);

It is recommended that device identifiers be dynamically assigned by using the alloc_chrdev_


region() function. This function allocates a range of char device numbers. The major number will be
chosen dynamically and returned (along with the first minor number) in dev. This function returns
zero or a negative error code.
int alloc_chrdev_region(dev_t* dev, unsigned baseminor, unsigned count, const char* name);

[ 72 ]
Chapter 4 Character Drivers

See below the description of the arguments of the previous function:


• dev: Output parameter for first assigned number.
• baseminor: First of the requested range of minor numbers.
• count: Number of minor numbers required.
• name: Name of the associated device or driver.
In the line of code below, the second parameter reserves a number (my_minor_count) of devices,
starting with my_major major and my_first_minor minor. The first parameter of the register_chrdev_
region() function is the first identifier of the device. The successive identifiers can be retrieved by
using the MKDEV macro.
register_chrdev_region(MKDEV(my_major, my_first_minor), my_minor_count, "my_device_driver");

After assigning the identifiers, the character device will have to be initialized by using the cdev_
init() function and registered to the kernel by using the cdev_add() function. The cdev_init() and
cdev_add() functions will be called as many times as assigned device identifiers.
The following sequence registers and initializes a number (MY_MAX_MINORS) of devices:
#include <linux/fs.h>
#include <linux/cdev.h>

#define MY_MAJOR 42
#define MY_MAX_MINORS 5

struct my_device_data {
struct cdev cdev;
/* my data starts here */
[...]
};

struct my_device_data devs[MY_MAX_MINORS];

const struct file_operations my_fops = {


.owner = THIS_MODULE,
.open = my_open,
.read = my_read,
.write = my_write,
.release = my_release,
.unlocked_ioctl = my_ioctl
};

[ 73 ]
Character Drivers Chapter 4

int init_module(void)
{
int i, err;
register_chrdev_region(MKDEV(MY_MAJOR, 0), MY_MAX_MINORS, "my_device_driver");

for(i = 0; i < MY_MAX_MINORS; i++) {


/* initialize devs[i] fields and register character devices */
cdev_init(&devs[i].cdev, &my_fops);
cdev_add(&devs[i].cdev, MKDEV(MY_MAJOR, i), 1);
}

return 0;
}

The following code snippet deletes and unregisters the character devices:
void cleanup_module(void)
{
int i;

for(i = 0; i < MY_MAX_MINORS; i++) {


/* release devs[i] fields */
cdev_del(&devs[i].cdev);
}
unregister_chrdev_region(MKDEV(MY_MAJOR, 0), MY_MAX_MINORS);
}

The main code sections of the driver will now be described:


1. Include the folowing header files to support character devices:
#include <linux/cdev.h>
#include <linux/fs.h>

2. Define the major number:


#define MY_MAJOR_NUM 202

3. One of the first things your driver will need to do when setting up a char device is to
obtain one or more device identifiers (major and minor numbers) to work with. The
necessary function for this task is register_chrdev_region(), which is declared in include/linux/
fs.h in the kernel source tree. Add the following lines of code to the hello_init() function to
allocate the device numbers when the module is loaded. The MKDEV macro will combine
a major number and a minor number to a dev_t data type that is used to hold the first
device identifier.

[ 74 ]
Chapter 4 Character Drivers

dev_t dev = MKDEV(MY_MAJOR_NUM, 0); /* get first device identifier */


/*
* Allocates all the character device identifiers,
* only one in this case, the one obtained with the MKDEV macro
*/
register_chrdev_region(dev, 1, "my_char_device");

4. Add the following line of code to the hello_exit() function to return the devices when the
module is removed:
unregister_chrdev_region(MKDEV(MY_MAJOR_NUM, 0), 1);

5. Create a file_operations structure called my_dev_fops. This structure defines function


pointers for "opening", "reading from", "writing to" the device, etc.
static const struct file_operations my_dev_fops = {
.owner = THIS_MODULE,
.open = my_dev_open,
.release = my_dev_close,
.unlocked_ioctl = my_dev_ioctl,
};

6. Implement each of the callback functions that are declared in the file_operations structure:
static int my_dev_open(struct inode *inode, struct file *file)
{
pr_info("my_dev_open() is called.\n");
return 0;
}

static int my_dev_close(struct inode *inode, struct file *file)


{
pr_info("my_dev_close() is called.\n");
return 0;
}

static long my_dev_ioctl(struct file *file, unsigned int cmd,


unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}

7. Add these file operation functionalities to your character device. The Linux kernel uses
struct cdev to represent character devices internally; therefore you will create a struct cdev
variable called my_dev and initialize it by using the cdev_init() function call, which takes
the variable my_dev and the structure my_dev_fops as parameters. Once the cdev structure
is setup, you will tell the kernel about it by using the cdev_add() function. You will call
cdev_init() and cdev_add() as many times as allocated character device identifiers (only once
in this driver).

[ 75 ]
Character Drivers Chapter 4

static struct cdev my_dev;


cdev_init(&my_dev, &my_dev_fops);
ret= cdev_add(&my_dev, dev, 1);

8. Add the following line of code to the hello_exit() function to delete the cdev structure:
cdev_del(&my_dev);

9. Once the kernel module has been dynamically loaded, the user needs to create a device
node to reference the driver. Linux provides the mknod utility for this purpose. The mknod
command has four parameters. The first parameter is the name of the device node that will
be created. The second parameter indicates whether the driver to which the device node
interfaces is a block driver or character driver. The last two parameters passed to mknod
are the major and minor numbers. Assigned major numbers are listed in the /proc/devices
file and can be viewed using the cat command. The created device node should be placed
in the /dev directory.
10. Create a new helloword_rpi3_char_driver.c file in the linux_5.4_rpi3_drivers folder, and write
the Listing 4-1 code on it. Add helloword_rpi3_char_driver.o to your Makefile obj-m variable.
11. In the linux_5.4_rpi3_drivers folder, you will create the apps folder, where you are going to
store most of the applications developed through this book.
~/linux_5.4_rpi3_drivers$ mkdir apps

12. In the apps folder, you will create an ioctl_test.c file, then write the Listing 4-3 code on it.
You will also create a Makefile (Listing 4-2) file in the apps folder to compile and deploy the
application.
13. Compile the helloworld_rpi3_char_driver.c driver and the ioctl_test.c application, and deploy
them to the Raspberry Pi:
~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy
~/linux_5.4_rpi3_drivers/apps$ make
~/linux_5.4_rpi3_drivers/apps$ make deploy

Listing 4-1: helloworld_rpi3_char_driver.c


#include <linux/module.h>

/* Add header files to support character devices */


#include <linux/cdev.h>
#include <linux/fs.h>

/* Define mayor number */


#define MY_MAJOR_NUM 202

[ 76 ]
Chapter 4 Character Drivers

static struct cdev my_dev;

static int my_dev_open(struct inode *inode, struct file *file)


{
pr_info("my_dev_open() is called.\n");
return 0;
}

static int my_dev_close(struct inode *inode, struct file *file)


{
pr_info("my_dev_close() is called.\n");
return 0;
}

static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}

/* Declare a file_operations structure */


static const struct file_operations my_dev_fops = {
.owner = THIS_MODULE,
.open = my_dev_open,
.release = my_dev_close,
.unlocked_ioctl = my_dev_ioctl,
};

static int __init hello_init(void)


{
int ret;

/* Get first device identifier */


dev_t dev = MKDEV(MY_MAJOR_NUM, 0);
pr_info("Hello world init\n");

/* Allocate a number of devices */


ret = register_chrdev_region(dev, 1, "my_char_device");
if (ret < 0) {
pr_info("Unable to allocate mayor number %d\n", MY_MAJOR_NUM);
return ret;
}

/* Initialize the cdev structure and add it to kernel space */


cdev_init(&my_dev, &my_dev_fops);
ret= cdev_add(&my_dev, dev, 1);
if (ret < 0) {
unregister_chrdev_region(dev, 1);
pr_info("Unable to add cdev\n");
return ret;
}

return 0;
}

[ 77 ]
Character Drivers Chapter 4

static void __exit hello_exit(void)


{
pr_info("Hello world exit\n");
cdev_del(&my_dev);
unregister_chrdev_region(MKDEV(MY_MAJOR_NUM, 0), 1);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a module that interacts with the ioctl system call");

Listing 4-2: Makefile


CC = arm-linux-gnueabihf-gcc
all: ioctl_test

app : ioctl_test.c
$(CC) -o $@ $^
clean :
rm ioctl_test
deploy : ioctl_test
scp $^ [email protected]:/home/pi

Listing 4-3: ioctl_test.c


#include <stdio.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
/* First, you need run "mknod /dev/mydev c 202 0" to create /dev/mydev */

int my_dev = open("/dev/mydev", 0);

if (my_dev < 0) {
perror("Fail to open device file: /dev/mydev.");
} else {
ioctl(my_dev, 100, 110); /* cmd = 100, arg = 110. */
close(my_dev);
}

return 0;
}

[ 78 ]
Chapter 4 Character Drivers

helloworld_rpi3_char_driver.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod helloworld_rpi3_char_driver.ko
Hello world init
See the allocated "my_char_device". The device mydev is not created under /dev yet:
root@raspberrypi:/home/pi# cat /proc/devices
Character devices:
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
[...]
202 my_char_device
[...]
root@raspberrypi:/home/pi# ls –l /dev/
Create mydev under /dev using mknod, and verify its creation:
root@raspberrypi:/home/pi# mknod /dev/mydev c 202 0
root@raspberrypi:/home/pi# ls -l /dev/mydev
crw-r--r-- 1 root root 202, 0 Apr 8 19:00 /dev/mydev
Run the application ioctl_test:
root@raspberrypi:/home/pi# ./ioctl_test
my_dev_open() is called.
my_dev_ioctl() is called. cmd = 100, arg = 110
my_dev_close() is called.
Remove the module:
root@raspberrypi:/home/pi# rmmod helloworld_rpi3_char_driver.ko
Hello world exit

Creating device files with devtmpfs


Before Linux 2.6.32, on basic Linux systems, the device files had to be created manually by using the mknod
command. The coherency between device files and devices handled by the kernel was left to the system
developer. With the release of the 2.6 series of stable kernel, a new ram-based filesystem called sysfs came
about. The job of sysfs is to export a view of the system's hardware configuration to the user space processes.
Linux drivers compiled into the kernel register their objects with a sysfs as they are detected by the kernel.
For Linux drivers compiled as modules, this registration will happen when the module is loaded. Sysfs is
enabled and ready to be used via the Linux kernel configuration CONFIG_SYSFS, which should be set to
yes by default.

[ 79 ]
Character Drivers Chapter 4

Device files are created by the kernel via the temporary devtmpfs filesystem. Any driver that wishes to
register a device node will use devtmpfs (via the core driver) to do it. When a devtmpfs instance is mounted
on /dev, the device node will initially be created with a fixed name, permissions and owner. These entries
can be both read from and written to. All device nodes are owned by root and have the default mode of
0600.
Shortly afterward, the kernel will send an uevent to udevd. Based on the rules specified in the files within the
/etc/udev/rules.d/, /lib/udev/rules.d/ and /run/udev/rules.d/ directories, udevd will create additional symlinks
to the device node, change its permissions, owner or group, or modify the internal udevd database entry
(name) for that object. The rules in these three directories are numbered, and all three directories are merged
together. If udevd can't find a rule for the device it is creating, it will leave the permissions and ownership at
whatever devtmpfs used initially.
The CONFIG_DEVTMPFS_MOUNT kernel configuration option makes the kernel mounts devtmpfs
automatically at boot time, except when booting on an initramfs.

LAB 4.2: "class character" module


In this kernel module lab, you will use your previous helloworld_rpi3_char_driver driver as a
starting point, but this time, the device node will be created by using devtmpfs instead of doing it
manually.
You will add an entry in the /sys/class/ directory. The /sys/class/ directory offers a view of the device
drivers grouped by classes.
When the register_chrdev_region() function tells the kernel that there is a driver with a specific major
number, it doesn't specify anything about the type of driver, so it will not create an entry under
/sys/class/. This entry is necessary so that devtmpfs can create a device node under /dev. Drivers
will have a class name and a device name under the /sys folder for each created device.
A Linux driver creates/destroys the class by using the following kernel APIs:
class_create() /* creates a class for your devices visible in /sys/class/ */
class_destroy() /* removes the class */

A Linux driver creates the device nodes by using the following kernel APIs:
device_create() /* creates a device node in the /dev directory */
device_destroy() /* removes a device node in the /dev directory */

The main points that differ from your previous helloworld_rpi3_char_driver driver will now be
described:
1. Include the following header file to create the class and device files:
#include <linux/device.h> /* class_create(), device_create() */

[ 80 ]
Chapter 4 Character Drivers

2. Your driver will have a class name and a device name; hello_class is used as the class name
and mydev as the device name. This results in the creation of a device that appears on the
file system at /sys/class/hello_class/mydev. Add the following definitions for the device and
class names:
#define DEVICE_NAME "mydev"
#define CLASS_NAME "hello_class"

3. The hello_init() function is longer than the one written in the helloworld_rpi3_char_driver
driver. That is because it now automatically allocates a major number to the device by
using the function alloc_chrdev_region(), as well as registering the device class and creating
the device node.
static int __init hello_init(void)
{
dev_t dev_no;
int Major;
struct device* helloDevice;

/* Allocate dynamically device numbers (only one in this driver) */


ret = alloc_chrdev_region(&dev_no, 0, 1, DEVICE_NAME);

/*
* Get the device identifiers using MKDEV. We are doing this
* for teaching purposes, as we only use one identifier in this
* driver and dev_no could be used as a parameter for cdev_add()
* and device_create() without needing to use the MKDEV macro
*/

/* Get the mayor number from the first device identifier */


Major = MAJOR(dev_no);

/* Get the first device identifier, that matches with dev_no */


dev = MKDEV(Major,0);

/* Initialize the cdev structure, and add it to kernel space */


cdev_init(&my_dev, &my_dev_fops);
ret = cdev_add(&my_dev, dev, 1);

/* Register the device class */


helloClass = class_create(THIS_MODULE, CLASS_NAME);

/* Create a device node named DEVICE_NAME associated to dev */


helloDevice = device_create(helloClass, NULL, dev, NULL, DEVICE_NAME);

return 0;
}

4. Create a new helloword_ rpi3_class_driver.c file in the linux_5.4_rpi3_drivers folder, and add
helloword_ rpi3_class_driver.o to your Makefile obj-m variable, then build and deploy the
module to the Raspberry Pi:

[ 81 ]
Character Drivers Chapter 4

~/linux_5.4_rpi3_drivers$ make
~/linux_5.4_rpi3_drivers$ make deploy

Listing 4-4: helloworld_rpi3_class_driver.c


#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>

#define DEVICE_NAME "mydev"


#define CLASS_NAME "hello_class"

static struct class* helloClass;


static struct cdev my_dev;
dev_t dev;

static int my_dev_open(struct inode *inode, struct file *file)


{
pr_info("my_dev_open() is called.\n");
return 0;
}

static int my_dev_close(struct inode *inode, struct file *file)


{
pr_info("my_dev_close() is called.\n");
return 0;
}

static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}

/* Declare a file_operations structure */


static const struct file_operations my_dev_fops = {
.owner = THIS_MODULE,
.open = my_dev_open,
.release = my_dev_close,
.unlocked_ioctl = my_dev_ioctl,
};

static int __init hello_init(void)


{
int ret;
dev_t dev_no;
int Major;
struct device* helloDevice;

pr_info("Hello world init\n");

[ 82 ]
Chapter 4 Character Drivers

/* Allocate device numbers dynamically */


ret = alloc_chrdev_region(&dev_no, 0, 1, DEVICE_NAME);
if (ret < 0) {
pr_info("Unable to allocate Mayor number \n");
return ret;
}

/* Get the device identifiers */


Major = MAJOR(dev_no);
dev = MKDEV(Major,0);

pr_info("Allocated correctly with major number %d\n", Major);

/* Initialize the cdev structure and add it to kernel space */


cdev_init(&my_dev, &my_dev_fops);
ret = cdev_add(&my_dev, dev, 1);
if (ret < 0) {
unregister_chrdev_region(dev, 1);
pr_info("Unable to add cdev\n");
return ret;
}

/* Register the device class */


helloClass = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(helloClass)) {
unregister_chrdev_region(dev, 1);
cdev_del(&my_dev);
pr_info("Failed to register device class\n");
return PTR_ERR(helloClass);
}
pr_info("device class registered correctly\n");

/* Create a device node named DEVICE_NAME associated to dev */


helloDevice = device_create(helloClass, NULL, dev, NULL, DEVICE_NAME);
if (IS_ERR(helloDevice)) {
class_destroy(helloClass);
cdev_del(&my_dev);
unregister_chrdev_region(dev, 1);
pr_info("Failed to create the device\n");
return PTR_ERR(helloDevice);
}
pr_info("The device is created correctly\n");

return 0;
}

static void __exit hello_exit(void)


{
device_destroy(helloClass, dev); /* remove the device */
class_destroy(helloClass); /* remove the device class */
cdev_del(&my_dev);
unregister_chrdev_region(dev, 1); /* unregister the device numbers */
pr_info("Hello world with parameter exit\n");
}

[ 83 ]
Character Drivers Chapter 4

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is a module that interacts with the ioctl system call");

helloworld_rpi3_class_driver.ko demonstration
Reboot your Raspberry Pi to remove the mydev device that you created manually in the previous
lab:
root@raspberrypi:/home/pi# reboot
Load the module:
root@raspberrypi:/home/pi# insmod helloworld_rpi3_class_driver.ko
Hello world init
Allocated correctly with major number 238
device class registered correctly
The device is created correctly
Check that hello_class and mydev are created, and see the entries under mydev:
root@raspberrypi:/home/pi# ls /sys/class
bcm2708_vcio hello_class iscsi_session raw thermal
bcm2835-gpiomem hidraw iscsi_transport rc tty
bdi hwmon leds regulator uio
block i2c-adapter lirc rfkill vc
bluetooth i2c-dev mdio_bus rtc vchiq
bsg ieee80211 mem scsi_device vc-mem
devcoredump input misc scsi_disk vc-sm
dma iscsi_connection mmc_host scsi_host video4linux
dma_heap iscsi_endpoint net sound vtconsole
gpio iscsi_host power_supply spi_master watchdog
graphics iscsi_iface pwm spi_slave
root@raspberrypi:/home/pi# ls -l /dev/mydev
crw------- 1 root root 238, 0 Apr 8 18:56 /dev/mydev
root@raspberrypi:/home/pi# ls /sys/class/hello_class/mydev
dev power subsystem uevent
See the assigned mayor and minor numbers for the device mydev:
root@raspberrypi:/home/pi# cat /sys/class/hello_class/mydev/dev
238:0
Run the application ioctl_test:
root@raspberrypi:/home/pi# ./ioctl_test
my_dev_open() is called.
my_dev_ioctl() is called. cmd = 100, arg = 110
my_dev_close() is called.
Remove the module:

[ 84 ]
Chapter 4 Character Drivers

root@raspberrypi:/home/pi# rmmod helloworld_rpi3_class_driver.ko


Hello world with parameter exit

Miscellaneous character driver


The Misc Framework is an interface exported by the Linux kernel that allows modules to register
their individual minor numbers.
The device driver implemented as a miscellaneous character uses the major number allocated
by the Linux kernel for miscellaneous devices. This eliminates the need to define a unique
major number for the driver; this is important, as a conflict between major numbers has become
increasingly likely, and use of the misc device class is an effective tactic. Each probed device is
dynamically assigned a minor number and is listed with a directory entry within the sysfs pseudo-
filesystem under /sys/class/misc/.
Major number 10 is officially assigned to the misc driver. Modules can register individual minor
numbers with the misc driver and take care of a small device, needing only a single entry point.

Registering a minor number


A miscdevice structure is declared in include/linux/miscdevice.h in the kernel source tree:
struct miscdevice {
int minor;
const char *name;
const struct file_operations *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
umode_t mode;
};

Where:
• minor is the minor number being registered.
• name is the name for this device, found in the /proc/misc file.
• fops is a pointer to the file_operations structure.
• parent is a pointer to a device structure that represents the hardware device exposed by this
driver.
The misc driver exports two functions, misc_register() and misc_deregister(), to register and
unregister their own minor number. These functions are declared in include/linux/miscdevice.h and
defined in drivers/char/misc.c in the kernel source tree:
int misc_register(struct miscdevice *misc);
int misc_deregister(struct miscdevice *misc);

[ 85 ]
Character Drivers Chapter 4

The misc_register() function registers a miscellaneous device with the kernel. If the minor number
is set to MISC_DYNAMIC_MINOR, a minor number is dynamically assigned and placed in the
minor field of the miscdevice structure. In other cases, the minor number requested is used.
The structure passed as an argument is linked into the kernel and may not be destroyed until it has
been unregistered. By default, an open() syscall to the device sets the file->private_data to point to
the structure. A zero is returned on success and a negative errno code for failure.
The typical code snippet for assigning a dynamic minor number is as follows:
static struct miscdevice my_dev;

int init_module(void)
{
my_dev.minor = MISC_DYNAMIC_MINOR;
my_dev.name = "my_device";
my_dev.fops = &my_fops;
misc_register(&my_dev);
pr_info("my: got minor %i\n", my_dev.minor);
return 0;
}

LAB 4.3: "miscellaneous character" module


In this lab, you will use your previous helloworld_rpi3_char_driver driver as a starting point. You
will achieve the same result through the misc framework, but you will write fewer lines of code!!
The main code sections of the driver will now be described:
1. Add the header file that declares the miscdevice structure:
#include <linux/miscdevice.h>

2. Initialize the miscdevice structure:


static struct miscdevice helloworld_miscdevice = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mydev",
.fops = &my_dev_fops,
}

3. Register and unregister the device with the kernel:


misc_register(&helloworld_miscdevice);
misc_deregister(&helloworld_miscdevice);

4. Create a new misc_ rpi3_driver.c file in the linux_5.4_rpi3_drivers folder, and add misc_ rpi3_
driver.o to your Makefile obj-m variable, then build and deploy the module to the Raspberry
Pi:
~/linux_5.4_rpi3_drivers$ make

[ 86 ]
Chapter 4 Character Drivers

~/linux_5.4_rpi3_drivers$ make deploy

Listing 4-5: misc_rpi3_driver.c


#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>

static int my_dev_open(struct inode *inode, struct file *file)


{
pr_info("my_dev_open() is called.\n");
return 0;
}

static int my_dev_close(struct inode *inode, struct file *file)


{
pr_info("my_dev_close() is called.\n");
return 0;
}

static long my_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
pr_info("my_dev_ioctl() is called. cmd = %d, arg = %ld\n", cmd, arg);
return 0;
}

static const struct file_operations my_dev_fops = {


.owner = THIS_MODULE,
.open = my_dev_open,
.release = my_dev_close,
.unlocked_ioctl = my_dev_ioctl,
};

/* Declare and initialize struct miscdevice */


static struct miscdevice helloworld_miscdevice = {
.minor = MISC_DYNAMIC_MINOR,
.name = "mydev",
.fops = &my_dev_fops,
};

static int __init hello_init(void)


{
int ret_val;
pr_info("Hello world init\n");

/* Register the device with the kernel */


ret_val = misc_register(&helloworld_miscdevice);

if (ret_val != 0) {
pr_err("could not register the misc device mydev");
return ret_val;
}

[ 87 ]
Character Drivers Chapter 4

pr_info("mydev: got minor %i\n",helloworld_miscdevice.minor);


return 0;
}

static void __exit hello_exit(void)


{
pr_info("Hello world exit\n");

/* Unregister the device with the Kernel */


misc_deregister(&helloworld_miscdevice);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Alberto Liberal <[email protected]>");
MODULE_DESCRIPTION("This is the helloworld_char_driver using misc framework");

misc_rpi3_driver.ko demonstration
Load the module:
root@raspberrypi:/home/pi# insmod misc_rpi3_driver.ko
Hello world init
mydev: got minor 60
Check that mydev is created under the misc class folder and under the /dev folder:
root@raspberrypi:/home/pi# ls /sys/class/misc
autofs cpu_dma_latency hw_random mydev vcsm-cma
cachefiles fuse loop-control rfkill watchdog
root@raspberrypi:/home/pi# ls -l /dev/mydev
crw------- 1 root root 10, 60 Apr 8 18:59 /dev/mydev
Run the application ioctl_test:
root@raspberrypi:/home/pi# ./ioctl_test
my_dev_open() is called.
my_dev_ioctl() is called. cmd = 100, arg = 110
my_dev_close() is called.
Remove the module:
root@raspberrypi:/home/pi# rmmod misc_rpi3_driver.ko
Hello world exit

[ 88 ]

You might also like