Creating device in linux

Device File Creation – Linux Device Driver Tutorial Part 5

This article is a continuation of the Series on Linux Device Driver and carries the discussion on character drivers and their implementation. The aim of this series is to provide easy and practical examples that anyone can understand. This is the Device File Creation for Character Drivers – Linux Device Driver Tutorial Part 5.

Device File Creation for Character Drivers

In our last tutorial, we have seen how to assign a major and minor number. But if you see, it will create a major and minor number. But I did not create any device files under /dev/ directory. The device file is important to communicate with the hardware. Let’s start our tutorial.

Device Files

The device file allows transparent communication between user-space applications and hardware.

They are not normal “files”, but look like files from the program’s point of view: you can read from them, write to them, mmap() onto them, and so forth. When you access such a device “file,” the kernel recognizes the I/O request and passes it to a device driver, which performs some operation, such as reading data from a serial port or sending data to hardware.

Device files (although inappropriately named, we will continue to use this term) provide a convenient way to access system resources without requiring the application programmer to know how the underlying device works. Under Linux, as with most Unix systems, device drivers themselves are part of the kernel.

All device files are stored in /dev directory. Use ls command to browse the directory:

ls -l /dev/

Each device on the system should have a corresponding entry in /dev . For example, /dev/ttyS0 corresponds to the first serial port, known as COM1 under MS-DOS ; /dev/hda2 corresponds to the second partition on the first IDE drive. In fact, there should be entries in /dev for devices you do not have. The device files are generally created during system installation and include every possible device driver. They don’t necessarily correspond to the actual hardware on your system. There are a number of pseudo-devices in /dev that don’t correspond to any actual peripheral. For example, /dev/null acts as a byte sink; any write request to /dev/null will succeed, but the data are written will be ignored.

When using ls -l to list device files in /dev , you’ll see something like the following:

First of all, note that the first letter of the permissions field is denoted that driver type. Device files are denoted either by b, for block devices, or c, for character devices.

Also, note that the size field in the ls -l listing is replaced by two numbers, separated by a comma. The first value is the major device number and the second is the minor device number. This we have discussed in the previous tutorial.

Creating Device File

We can create a dive file in two ways.

We will discuss these topics one by one.

Manually Creating Device File

We can create the device file manually by using mknod .

name > – your device file name that should have a full path ( /dev/name )

device type > – Put c or b

c – Character Device

b – Block Device

major > – major number of your driver

minor > – minor number of your driver

-m permissions > – optional argument that sets the permission bits of the new device file to permissions

Example:

If you don’t want to give permission, You can also use chmod to set the permissions for a device file after creation.

Advantages

  • Anyone can create the device file using this method.
  • You can create the device file even before loading the driver.

Programming

I took this program from the previous tutorial. I’m going to create a device file manually for this driver.

[Get the Source code from GitHub]

  • Build the driver by using Makefile (sudo make)
  • Load the driver using sudo insmod
  • Check the device file using ls -l /dev/ . By this time device file is not created for your driver.
  • Create a device file using mknod and then check using ls -l /dev/ .
  • Now our device file got created and registered with a major number and minor number.
  • Unload the driver using sudo rmmod

Automatically Creating Device File

The automatic creation of device files can be handled with udev . Udev is the device manager for the Linux kernel that creates/removes device nodes in the /dev directory dynamically. Just follow the below steps.

  1. Include the header file linux/device.h and linux/kdev_t.h
  2. Create the struct Class
  3. Create Device with the class which is created by the above step

Create the class

This will create the struct class for our device driver. It will create a structure under /sys/class/ .

struct class * class_create (struct module *owner, const char *name);

owner – pointer to the module that is to “ own ” this struct class

name – pointer to a string for the name of this class

This is used to create a struct class pointer that can then be used in calls to class_device_create .

Note, the pointer created here is to be destroyed when finished by making a call to class_destroy .

void class_destroy (struct class * cls);

Create Device

This function can be used by char device classes. A struct device will be created in sysfs, registered to the specified class.

struct device *device_create (struct *class, struct device *parent, dev_t dev, void * drvdata, const char *fmt, . );

class – pointer to the struct class that this device should be registered to

parent – pointer to the parent struct device of this new device, if any

devt – the dev_t for the char device to be added

drvdata – the data to be added to the device for callbacks

fmt – string for the device’s name

. – variable arguments

A “ dev ” file will be created, showing the dev_t for the device, if the dev_t is not 0,0. If a pointer to a parent struct device is passed in, the newly created struct device will be a child of that device in sysfs. The pointer to the struct device will be returned from the call. Any further sysfs files that might be required can be created using this pointer.

Note, you can destroy the device using device_destroy() .

void device_destroy (struct class * class, dev_t devt);

If you don’t understand please refer to the below program, Then you will get some idea.

Programming

[Get the source code from GitHub]

  • Build the driver by using Makefile (sudo make)
  • Load the driver using sudo insmod
  • Check the device file using ls -l /dev/ | grep «etx_device»
  • Unload the driver using sudo rmmod

In this tutorial, we have created the device file manually and automatically. So, using this device file we can communicate to the hardware. In our next tutorial, we will show how to open, read, write, and close the device file.

Please find the other Linux device driver tutorials here.

Источник

In Linux, how do you use device_create within an existing class?

Note: I’m listing this problem as it is today, I’m not opposed to changing the implementation (moving the creation of the class to a common area for example) if it makes things easier. I’m just not sure how to do it. :End Note

I’ve got two linux kernel modules and I’m trying to update the /sys entries for them. Searching around on google and other sources, I’ve seen lots of code along the lines of:

And I’ve verified for my first module this code works, and that it correctly creates a:

entry. What I’d like to know is how do you create a device in an existing class. In other words, one of my modules created this new chardrv class, now I want my other module to be able to also register its devices under the same class.

I can’t call class_create() again (in the second module), because that «chardrv» class already exists.

So I can run a check to see if /sys/class/chardrv exists, and this can help me decide if I need to call class_create() or not, that’s not a problem. Lets put some pseudo code in here to clarify:

So as per this example, if my class already exists, and I just want to add my new device into it from a second module I assume I need to create a class structure and somehow populate it with the correct «chardrv class» attributes then call device_create as before, but I’m not sure how to do that.

Источник

How to create virtual block device (loop device/filesystem) in Linux

Linux supports a special block device called the loop device, which maps a normal file onto a virtual block device. This allows for the file to be used as a “virtual file system” inside another file. With Linux it’s possible to create a file-system inside a single file. These storage devices are available as device files such as /dev/device_name.

Create a file

1. First step is to create a file of desired size. The following command will create a file that is 1 GB in size:

2. Verify the size of the file you have just created.

Create the loop device

1. Next step is to create a loop device with the file. Use the command “losetup” to create a loop device “loop0”

Here,
-f – find the first unused loop device. If a file argument is present, use this device. Otherwise, print its name.
-P – force kernel to scan partition table on newly created loop device.

2. To print the loop device generated using the above command use “losetup -a”.

Create the filesystem

1. Now lets create a ext4 filesystem on the loopback device.

Mount the loopback filesystem

1. We can now mount the loopback filesystem onto a directory. The “-o loop” additional option is used to mount loopback filesystems.

2. Verify the size of the new mount point and type of filesystem using below commands.

Removing loop device

If you want remove the new filesystem, use the following steps:
1. Umount and delete the directory /loopfs

2. Delete the loopback device “loop0” created using the “losetup -d” command.

3. Finally remove the file “/root/loopbackfile.img” used to create the loop device.

Enable Encryption on loop filesystem

‘losetup’ also allows to enable data encryption in order to get a crypted filesystem. The syntax to created a encrypted loop device si as shown below:

The following encryption algorithms are accepted:

  • NONE use no encryption (default).
  • XOR use a simple XOR encryption.
  • DES use DES encryption.

DES encryption is only available if the optional DES package has been added to the kernel. DES encryption uses an additional start value that is used to protect passwords against dictionary attacks.

Источник

Linux Device Drivers: Tutorial for Linux Driver Development

Programming a device driver for Linux requires a deep understanding of the operating system and strong development skills. To help you master this complex domain, Apriorit driver development experts created this tutorial.

We’ll show you how to write a device driver for Linux (5.3.0 version of the kernel). In doing so, we’ll discuss the kernel logging system, principles of working with kernel modules, character devices, the file_operations structure, and accessing user-level memory from the kernel. You’ll also get code for a simple Linux driver that you can augment with any functionality you need.

This article will be useful for developers studying Linux driver development.

Driver Development Team

Contents:

Getting started with the Linux kernel module

The Linux kernel is written in the C and Assembler programming languages. C implements the main part of the kernel, while Assembler implements architecture-dependent parts. That’s why we can use only these two languages for Linux device driver development. We cannot use C++, which is used for the Microsoft Windows kernel, because some parts of the Linux kernel source code (e.g. header files) may include keywords from C++ (for example, delete or new ), while in Assembler we may encounter lexemes such as ‘ : : ’ .

There are two ways of programming a Linux device driver:

  1. Compile the driver along with the kernel, which is monolithic in Linux.
  2. Implement the driver as a kernel module, in which case you won’t need to recompile the kernel.

In this tutorial, we’ll develop a driver in the form of a kernel module. A module is a specifically designed object file. When working with modules, Linux links them to the kernel by loading them to the kernel address space.

Module code has to operate in the kernel context. This requires a developer to be very attentive. If a developer makes a mistake when implementing a user-level application, it will not cause problems outside the user application in most cases. But mistakes in the implementation of a kernel module will lead to system-level issues.

Luckily for us, the Linux kernel is resistant to non-critical errors in module code. When the kernel encounters such errors (for example, null pointer dereferencing), it displays the oops message — an indicator of insignificant malfunctions during Linux operation. After that, the malfunctioning module is unloaded, allowing the kernel and other modules to work as usual. In addition, you can analyze logs that precisely describe non-critical errors. Keep in mind that continuing driver execution after an oops message may lead to instability and kernel panic.

The kernel and its modules represent a single program module and use a single global namespace. In order to minimize the namespace, you must control what’s exported by the module. Exported global characters must have unique names and be cut to the bare minimum. A commonly used workaround is to simply use the name of the module that’s exporting the characters as the prefix for a global character name.

With this basic information in mind, let’s start writing our driver for Linux.

Creating a kernel module

We’ll start by creating a simple prototype of a kernel module that can be loaded and unloaded. We can do that with the following code:

The my_init function is the driver initialization entry point and is called during system startup (if the driver is statically compiled into the kernel) or when the module is inserted into the kernel. The my_exit function is the driver exit point. It’s called when unloading a module from the Linux kernel. This function has no effect if the driver is statically compiled into the kernel.

These functions are declared in the linux/module.h header file. The my_init and my_exit functions must have identical signatures such as these:

Now our simple module is complete. Let’s teach it to log in to the kernel and interact with device files. These operations will be useful for Linux kernel driver development.

Registering a character device

Device files are usually stored in the /dev folder. They facilitate interactions between the user space and the kernel code. To make the kernel receive anything, you can just write it to a device file to pass it to the module serving this file. Anything that’s read from a device file originates from the module serving it.

There are two groups of device files:

  1. Character files — Non-buffered files that allow you to read and write data character by character. We’ll focus on this type of file in this tutorial.
  2. Block files — Buffered files that allow you to read and write only whole blocks of data.

Linux systems have two ways of identifying device files:

  1. Major device numbers identify modules serving device files or groups of devices.
  2. Minor device numbers identify specific devices among a group of devices specified by a major device number.

We can define these numbers in the driver code, or they can be allocated dynamically. In case a number defined as a constant has already been used, the system will return an error. When a number is allocated dynamically, the function reserves that number to prevent other device files from using the same number.

To register a character device, we need to use the register_chrdev function:

Here, we specify the name and the major number of a device to register it. After that, the device and the file_operations structure will be linked. If we assign 0 to the major parameter, the function will allocate a major device number on its own. If the value returned is 0, this indicates success, while a negative number indicates an error. Both device numbers are specified in the 0–255 range.

The device name is a string value of the name parameter. This string can pass the name of a module if it registers a single device. We use this string to identify a device in the /sys/devices file. Device file operations such as read, write, and save are processed by the function pointers stored within the file_operations structure. These functions are implemented by the module, and the pointer to the module structure identifying this module is also stored within the file_operations structure (more about this structure in the next section).

The file_operations structure

In the Linux 5.3.0 kernel, the file_operations structure looks like this:

If this structure contains functions that aren’t required for your driver, you can still use the device file without implementing them. A pointer to an unimplemented function can simply be set to 0. After that, the system will take care of implementing the function and make it behave normally. In our case, we’ll just implement the read function.

As we’re going to ensure the operation of only a single type of device with our Linux driver, our file_operations structure will be global and static. After it’s created, we’ll need to fill it statically like this:

The declaration of the THIS_MODULE macro is contained in the linux/export.h header file. We’ll transform the macro into a pointer to the module structure of the required module. Later, we’ll write the body of the function with a prototype, but for now we have only the device_file_read pointer to it:

The file_operations structure allows us to develop several functions that will register and revoke the registration of the device file. To register a device file, we use the following code:

device_file_major_number is a global variable that contains the major device number. When the lifetime of the driver expires, this global variable will be used to revoke the registration of the device file.

In the code above, we’ve added the printk function that logs kernel messages. Pay attention to the KERN_NOTICE and KERN_WARNING prefixes in all listed printk format strings. NOTICE and WARNING indicate the priority level of a message. Levels range from insignificant ( KERN_DEBUG ) to critical ( KERN_EMERG ), alerting about kernel instability. This is the only difference between the printk function and the printf library function.

The printk function

The printk function forms a string, which we add to the circular buffer. From there the klog daemon reads it and sends it to the system log. Implementing the printk allows us to call this function from any point in the kernel. Use this function carefully, as it may cause overflow of the circular buffer, meaning the oldest message will not be logged.

Our next step is writing a function for unregistering the device file. If a device file is successfully registered, the value of the device_file_major_number will not be 0. This value allows us to revoke the registration of a file using the unregister_chrdev function, which we declare in the linux/fs.h file. The major device number is the first parameter of this function, followed by a string containing the device name. The register_chrdev and the unresister_chrdev functions have similar contents.

To unregister a device, we use the following code:

The next step in implementing functions for our module is allocating and using memory in user mode. Let’s see how it’s done.

Using memory allocated in user mode

The read function we’re going to write will read characters from a device. The signature of this function must be appropriate for the function from the file_operations structure:

Let’s look at the filep parameter — the pointer to the file structure. This file structure allows us to get necessary information about the file we’re working with, data related to this file, and more. The data we’ve read is allocated in the user space at the address specified by the second parameter — buffer. The number of bytes to be read is defined in the len parameter, and we start reading bytes from a certain offset defined in the offset parameter. After executing the function, the number of bytes that have been successfully read must be returned. Then we must refresh the offset.

To work with information from the device file, the user allocates a special buffer in the user-mode address space. Then, the read function copies the information to this buffer. The address to which a pointer from the user space points and the address in the kernel address space may have different values. That’s why we cannot simply dereference the pointer.

When working with these pointers, we have a set of specific macros and functions we declare in the linux/uaccess.h file. The most suitable function in our case is copy_to_user. Its name speaks for itself: it copies specific data from the kernel buffer to the buffer allocated in the user space. It also verifies if a pointer is valid and if the buffer size is large enough. Here’s the code for the copy_to_user prototype:

First of all, this function must receive three parameters:

  • A pointer to the buffer
  • A pointer to the data source
  • The number of bytes to be copied

If there are any errors in execution, the function will return a value other than 0. In case of successful execution, the value will be 0. The copy_to_user function contains the _user macro that documents the process. Also, this function allows us to find out if the code uses pointers from the address space correctly. This is done using Sparse, an analyzer for static code. To be sure that it works correctly, always mark the user address space pointers as _user.

Here’s the code for implementing the read function:

With this function, the code for our driver is ready. Now it’s time to build the kernel module and see if it works as expected.

Building the kernel module

In modern kernel versions, the makefile does most of the building for a developer. It starts the kernel build system and provides the kernel with information about the components required to build the module.

A module built from a single source file requires a single string in the makefile . After creating this file, you only need to initiate the kernel build system with the obj-m := source_file_name.o command. As you can see, here we’ve assigned the source file name to the module — the *.ko file.

If there are several source files, only two strings are required for the kernel build:

To initialize the kernel build system and build the module, we need to use the make –C KERNEL_MODULE_BUILD_SYSTEM_FOLDER M=`pwd` modules command. To clean up the build folder, we use the make –C KERNEL_MODULES_BUILD_SYSTEM_FOLDER M=`pwd` clean command.

The module build system is commonly located in /lib/modules/`uname -r`/build. Now it’s time to prepare the module build system. To build our first module, execute the make modules_prepare command from the folder where the build system is located.

Finally, we’ll combine everything we’ve learned into one makefile :

The load target loads the build module and the unload target deletes it from the kernel.

In our tutorial, we’ve used code from main.c and device_file.c to compile a driver. The resulting driver is named simple-module.ko. Let’s see how to use it.

Loading and using the module

To load the module, we have to execute the make load command from the source file folder. After this, the name of the driver is added to the /proc/modules file, while the device that the module registers is added to the /proc/devices file. The added records look like this:

The first three records contain the name of the added device and the major device number with which it’s associated. The minor number range (0–255) allows device files to be created in the /dev virtual file system.

Then we need to create the special character file for our major number with the mknod /dev/simple-driver c 250 0 command.

After we’ve created the device file, we need to perform the final verification to make sure that what we’ve done works as expected. To verify, we can use the cat command to display the device file contents:

If we see the contents of our driver, it works correctly!

Conclusion

In this tutorial, we’ve shown you how to write a simple Linux driver. You can find the full source code of this driver in the Apriorit GitHub repository. If you need a more complex device driver, you may use this tutorial as a basis and add more functions and context to it.

At Apriorit, we’ve made Linux kernel and driver development our speciality. Our developers have successfully delivered hundreds of complex drivers for Linux, Unix, macOS, and Windows. Contact our experienced team to start working on your next Linux driver development project!

Источник

Читайте также:  Сбросить настройки mac os big sur
Оцените статью