Cover image for Linux Character Device Basics

Linux Character Device Basics

Words 13.2k
Views
Visitors

Timeline

Timeline

2025-11-11

init

This article introduces the basics of Linux character device driver development, focusing on the composition and operation of device numbers. The article first explains the roles of major and minor device numbers, as well as the device number dev_The storage format of t in the Linux kernel (the high 12 bits are the major device number, and the low 20 bits are the minor device number). Subsequently, the usage of device number operation macros (MINORBITS, MINORMASK, MAJOR, MINOR, MKDEV) is introduced in detail, and the static device number allocation function register is compared._chrdev_region() and the dynamic device number allocation function alloc_chrdev_The parameters, return value, and applicable scenarios of region(), and also explains the device number release function unregister._The usage of chrdev_region(). Finally, the article introduces the core structure cdev of character devices, including the meaning of its fields and its registration and usage in the kernel, laying the foundation for later writing complete character device drivers.

Linux Driver Notes

Table of ContentsLinks
1. Linux Driver Framework
2. Linux Driver Loading Logic
3. Character Device Basics
4. Concurrency and Race Conditions
5. Advanced Character Device Topics
6. Interrupts
7. Platform Bus
8. Device Tree
9. Device Model
10. Hotplug
11. pinctrl Subsystem
12. GPIO subsystem
13. Input subsystem
14. 1-Wire
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. Network devices
23. ADC
24. IIO
25. USB
26. LCD

Device ID

In Linux systems, every device has a corresponding device number, and device numbers havemajor device numberandsecondary device numberThe distinction:

  • major device number: IdentifierDevice Type
  • secondary device number: atDistinguish between multiple device instances managed by the same driver

Example:

  • Major device number 13 represents input subsystem(Handling input devices).
    • Minor device number 64 →/dev/input/event0(possibly a USB keyboard)
    • Minor device number 65 →/dev/input/event1(possibly a USB mouse)
  • Major device number 188 represents USB serial device
    • Secondary device number 0 →/dev/ttyUSB0
    • Secondary device number 1 →/dev/ttyUSB1

BeforeBefore registering a character device driver, you need to apply for a device number first.

include/linux/types.h

1234567
typedef u32 __kernel_dev_t;typedef __kernel_fd_set		fd_set;typedef __kernel_dev_t		dev_t;typedef __kernel_ino_t		ino_t;typedef __kernel_mode_t		mode_t;typedef unsigned short		umode_t;

The device number dev_t is of type u32,The high 12 bits are the master device number.The lower 20 bits are the minor device number.

Device number operation macros

include/linux/kdev_t.h

123456
#define MINORBITS	20#define MINORMASK	((1U << MINORBITS) - 1)#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
  • MINORBITSIndicates the number of bits of the minor device number, 20 bits in total.
  • MINORMASKMinor device number mask, used to extract the minor device number from dev_t ((dev) & MINORMASK
  • MAJORIndicates from dev_t, get the major device number, essentially shifting dev_t shifted right by 20 bits
  • MINORIndicates getting the minor device number from dev_t, essentially taking the value of the lower 20 bits.
  • MKDEVUsed to combine the major device number and minor device number into a dev_t type device number.

Device number allocation function

In Linux drivers, the following two methods can be used to allocate device numbers.

include/linux/fs.h

123
extern int register_chrdev_region(dev_t, unsigned, const char *);extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);

register_chrdev_region()

  1. Through theregister_chrdev_region(dev_t from, unsigned count, const char *name)The function performs static device number allocation.

    • Function prototype
    1
    register_chrdev_region(dev_t from, unsigned count, const char *name)
    • Function purpose: Statically allocate device numbers, applying for a pre-specified device number.
    • Parameter meaning
      • from: A custom dev_t type device number, for example, MKDEV(100,0) indicates a starting major device number of 100 and a starting minor device number of 0.
      • count: The number of minor devices, indicating how many minor device numbers there are under the same major device number.
      • name: the name of the device to apply for
    • Function return value: Returns 0 on success, and a negative number on failure.

Note that statically allocated device numbers are limited:include/linux/fs.hdefined in:

1234567
/* fs/char_dev.c */#define CHRDEV_MAJOR_MAX 512/* Marks the bottom of the first segment of free char majors */#define CHRDEV_MAJOR_DYN_END 234/* Marks the top and bottom of the second segment of free char majors */#define CHRDEV_MAJOR_DYN_EXT_START 511#define CHRDEV_MAJOR_DYN_EXT_END 384

alloc_chrdev_region()

  1. Through thealloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char* name)The function performs dynamic device number allocation.
  • Function prototype
1
alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)
  • Function purpose: Dynamically allocate device numbers. The kernel automatically assigns an unused device number. Compared with static allocation, dynamic allocation avoids conflicts caused by registering the same device number.

  • Parameter meaning

    • dev *: will store the allocated device number in the dev variable
    • baseminor: the starting address of the minor device number. Minor device numbers usually start from 0, so this parameter is generally set to 0.
    • count: the number of devices to apply for
    • name: the name of the device to apply for
  • Function return value: Returns 0 on success, and a negative number on failure.

Device number release function

unregister_chrdev_region()

include/linux/fs.h

1
extern void unregister_chrdev_region(dev_t, unsigned);
  • Function: device number release function. After unregistering a character device, the device number must be released.
  • Function parameters:
    • dev_tType: the device number to be released
    • unsignedType: the number of device numbers to be released

example

my_driver.c

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/moduleparam.h>#include <linux/fs.h>#include <linux/types.h>static int major = 0;static int minor = 0;module_param(major, int, S_IRUGO);module_param(minor, int, S_IRUGO);static dev_t dev_id;static int __init my_driver_init(void){	int ret;	if (major) {		dev_id = MKDEV(major, minor);		printk(KERN_INFO "major from module_param: %d", MAJOR(dev_id));		printk(KERN_INFO "minor from module_param: %d", MINOR(dev_id));		ret = register_chrdev_region(dev_id, 1, "my_driver device");		if (ret < 0) {			printk(KERN_ERR "register_chrdev_region error\n");		} else {			printk(KERN_INFO "register_chrdev_region ok\n");		}	} else {		ret = alloc_chrdev_region(&dev_id, 0, 1, "my_driver device");		if (ret < 0) {			printk(KERN_ERR "alloc_chrdev_region error\n");		} else {			printk(KERN_INFO "alloc_chrdev_region ok\n");			printk(KERN_INFO "major allocated: %d", MAJOR(dev_id));			printk(KERN_INFO "minor allocated: %d", MINOR(dev_id));		}	}	printk(KERN_INFO "my_driver: Module loaded\n");	return 0;}static void __exit my_driver_exit(void){	unregister_chrdev_region(dev_id, 1);	printk(KERN_INFO "unregister_chrdev_region ok\n");	printk(KERN_INFO "my_driver: Module unloaded\n");}module_init(my_driver_init);module_exit(my_driver_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Zhao Hang");MODULE_DESCRIPTION("my_driver Kernel Module");

Test:

1234567891011121314151617181920212223242526272829303132
~ # insmod my_driver.ko[   18.414274] my_driver: loading out-of-tree module taints kernel.[   18.421489] alloc_chrdev_region ok[   18.421652] major allocated: 511[   18.421667] minor allocated: 0[   18.421910] my_driver: Module loaded~ # cat /proc/devices | grep my_driver511 my_driver device~ # rmmod my_driver.ko[   27.856484] unregister_chrdev_region ok[   27.856638] my_driver: Module unloaded~ #  cat /proc/devices | grep my_driver~ #~ # insmod my_driver.ko major=512 minor=0[   16.721502] my_driver: loading out-of-tree module taints kernel.[   16.728530] major from module_param: 512[   16.728574] minor from module_param: 0[   16.728753] CHRDEV "my_driver device" major requested (512) is greater than the maximum (511)[   16.729588] register_chrdev_region error[   16.729760] my_driver: Module loaded~ # rmmod my_driver.ko[   36.863583] unregister_chrdev_region ok[   36.864560] my_driver: Module unloaded~ # insmod my_driver.ko major=500 minor=0[   40.101231] major from module_param: 500[   40.101265] minor from module_param: 0[   40.101430] register_chrdev_region ok[   40.101657] my_driver: Module loaded~ # rmmod my_driver.ko[   50.626705] unregister_chrdev_region ok[   50.626876] my_driver: Module unloaded

Register character device

include/linux/cdev.h

123456789101112131415161718192021222324252627282930313233343536373839
/* SPDX-License-Identifier: GPL-2.0 */#ifndef _LINUX_CDEV_H#define _LINUX_CDEV_H#include <linux/kobject.h>#include <linux/kdev_t.h>#include <linux/list.h>#include <linux/device.h>struct file_operations;struct inode;struct module;struct cdev {	struct kobject kobj;	struct module *owner;	const struct file_operations *ops;	struct list_head list;	dev_t dev;	unsigned int count;} __randomize_layout;void cdev_init(struct cdev *, const struct file_operations *);struct cdev *cdev_alloc(void);void cdev_put(struct cdev *p);int cdev_add(struct cdev *, dev_t, unsigned);void cdev_set_parent(struct cdev *p, struct kobject *kobj);int cdev_device_add(struct cdev *cdev, struct device *dev);void cdev_device_del(struct cdev *cdev, struct device *dev);void cdev_del(struct cdev *);void cd_forget(struct inode *);#endif

In C, if you only need to use a certain struct’s pointer, and you don’t need to know its internal members (i.e., you don’t dereference it or use sizeof on it), you can just do forward declaration

1
struct file_operations;  // tells the compiler: there is a struct type called file_operations

This way you can define a pointer:

1
struct file_operations *fops;

but you cannot access members (because the compiler doesn’t know the struct contents yet):

1
fops->read(...);  // ❌ Compilation error: incomplete type

This is very common in header files, used to decouple dependencies and speed up compilation

AndexternYes Storage class specifier, used to declare variable or function of the external linkage attribute. Its purpose is to tell the compiler: “This variable/function is defined elsewhere; this is just a declaration.”

GCC/Clang attribute(__randomize_layout),
for the kernel Struct layout randomization, increasing the difficulty for attackers to predict structure offsets, thereby improving kernel security.

cdev structure

fieldmeaning
kobjinherited fromstruct kobject, so that character devices can be attached under sysfs (i.e./sys/class/...such as paths).
ownerPoints to the module that owns the device (THIS_MODULE), to prevent the device from still being in use when the module is unloaded.
opsPointer to device operation function table (struct file_operations), define read/write/ioctl and other behaviors.
listInternal linked list, shares all thecdevof the inode (i.e./dev/xxxfile) passedinode->i_devicesLinked together to form a “back-reference linked list”.
devDevice number, type isdev_t(including the major device number and minor device number).
countindicates thiscdevThe number of consecutive device numbers controlled (usually 1).

cdev_init

fs/char_dev.c

123456789101112131415
/** * cdev_init() - initialize a cdev structure * @cdev: the structure to initialize * @fops: the file_operations for this device * * Initializes @cdev, remembering @fops, making it ready to add to the * system with cdev_add(). */void cdev_init(struct cdev *cdev, const struct file_operations *fops){	memset(cdev, 0, sizeof *cdev);	INIT_LIST_HEAD(&cdev->list);	kobject_init(&cdev->kobj, &ktype_cdev_default);	cdev->ops = fops;}
  • Function: Initialize a statically allocatedstruct cdev. Establish the connection between cdev and file_operations.

Note: callcdev_initIt is best to call afterwardscdev_test.owner = THIS_MODULE;Pointing the owner field to this module can prevent the module from being unloaded while its operations are in use.

cdev_add

fs/char_dev.c

1234567891011121314151617181920212223242526272829
/** * cdev_add() - add a char device to the system * @p: the cdev structure for the device * @dev: the first device number for which this device is responsible * @count: the number of consecutive minor numbers corresponding to this *         device * * cdev_add() adds the device represented by @p to the system, making it * live immediately.  A negative error code is returned on failure. */int cdev_add(struct cdev *p, dev_t dev, unsigned count){	int error;	p->dev = dev;	p->count = count;	if (WARN_ON(dev == WHITEOUT_DEV))		return -EBUSY;	error = kobj_map(cdev_map, dev, count, NULL,			 exact_match, exact_lock, p);	if (error)		return error;	kobject_get(p->kobj.parent);	return 0;}
  • Function: Add a cdev structure to the system, that is, add a character device.
  • cdev_map: Globalkobj_mapStructure (hash table)
  • p: points tostruct cdev
  • Note:cdevEmbeddedstruct kobject kobj, socdevcan be viewed as akobject

cdev_del

fs/char_dev.c

12345678910111213141516
/** * cdev_del() - remove a cdev from the system * @p: the cdev structure to be removed * * cdev_del() removes @p from the system, possibly freeing the structure * itself. * * NOTE: This guarantees that cdev device will no longer be able to be * opened, however any cdevs already open will remain and their fops will * still be callable even after cdev_del returns. */void cdev_del(struct cdev *p){	cdev_unmap(p->dev, p->count);	kobject_put(&p->kobj);}
  • Function: delete a character device in the system

example

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/moduleparam.h>#include <linux/fs.h>#include <linux/types.h>#include <linux/cdev.h>static int major = 0;static int minor = 0;module_param(major, int, S_IRUGO);module_param(minor, int, S_IRUGO);static dev_t dev_id;static struct cdev cdev_test;static struct file_operations cdev_test_ops = {	.owner = THIS_MODULE,};static int __init my_driver_init(void){	int ret;	if (major) {		dev_id = MKDEV(major, minor);		pr_info( "major from module_param: %d", MAJOR(dev_id));		pr_info( "minor from module_param: %d", MINOR(dev_id));		ret = register_chrdev_region(dev_id, 1, "my_driver device");		if (ret < 0) {			pr_err( "register_chrdev_region error\n");			return ret;		} else {			pr_info( "register_chrdev_region ok\n");		}	} else {		ret = alloc_chrdev_region(&dev_id, 0, 1, "my_driver device");		if (ret < 0) {			pr_err( "alloc_chrdev_region error\n");			return ret;		} else {			pr_info( "alloc_chrdev_region ok\n");			pr_info( "major allocated: %d", MAJOR(dev_id));			pr_info( "minor allocated: %d", MINOR(dev_id));		}	}	cdev_init(&cdev_test, &cdev_test_ops);	//Pointing the owner field to this module can prevent the module from being unloaded while its operations are in use.	cdev_test.owner = THIS_MODULE;	ret = cdev_add(&cdev_test, dev_id, 1);	if (ret < 0) {		pr_err( "cdev_add error\n");		unregister_chrdev_region(dev_id, 1);		return ret;	}else{		pr_info( "cdev_add ok\n");	}	pr_info( "my_driver: Module loaded\n");	return 0;}static void __exit my_driver_exit(void){	cdev_del(&cdev_test);	unregister_chrdev_region(dev_id, 1);	pr_info( "unregister_chrdev_region ok\n");	pr_info( "my_driver: Module unloaded\n");}module_init(my_driver_init);module_exit(my_driver_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Zhao Hang");MODULE_DESCRIPTION("my_driver Kernel Module");

Note: delete the device first, then release the device number.

file_operations structure

Linux has a very important concept called ‘everything is a file’, meaning that devices in Linux are just like ordinary files. Accessing a device is like accessing a file. In applications, we can use system calls such as open, read, write, close, and ioctl to operate drivers. When we call the open function in an application, it ultimately executes the open function in the driver. Therefore, file_operations connects system calls with device drivers.

Calling the open function in the application

1
fd = open("/dev/hello", O_RDWR);

Executing the open function in the driver

123456789101112
static int cdev_test_open(struct inode *, struct file*){    printk("This is cdev_test open");    return 0;}static struct file_operations cdev_test_ops={    .owner = THIS_MODULE,    .open = cdev_test_open,    .read = cdev_test_read,    .write = cdev_test_write,    .release = cdev_test_release}

Definition of file_operations in Linux:

include/linux/fs.h

123456789101112131415161718192021222324252627282930313233343536373839404142
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 *);	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);	int (*iopoll)(struct kiocb *kiocb, bool spin);	int (*iterate) (struct file *, struct dir_context *);	int (*iterate_shared) (struct file *, struct dir_context *);	__poll_t (*poll) (struct file *, struct poll_table_struct *);	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);	int (*mmap) (struct file *, struct vm_area_struct *);	unsigned long mmap_supported_flags;	int (*open) (struct inode *, struct file *);	int (*flush) (struct file *, fl_owner_t id);	int (*release) (struct inode *, struct file *);	int (*fsync) (struct file *, loff_t, loff_t, int datasync);	int (*fasync) (int, struct file *, int);	int (*lock) (struct file *, int, struct file_lock *);	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);	int (*check_flags)(int);	int (*flock) (struct file *, int, struct file_lock *);	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);	int (*setlease)(struct file *, long, struct file_lock **, void**);	long (*fallocate)(struct file *file, int mode, loff_t offset,			  loff_t len);	void (*show_fdinfo)(struct seq_file *m, struct file *f);#ifndef CONFIG_MMU	unsigned (*mmap_capabilities)(struct file *);#endif	ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,			loff_t, size_t, unsigned int);	loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,				   struct file *file_out, loff_t pos_out,				   loff_t len, unsigned int remap_flags);	int (*fadvise)(struct file *, loff_t, loff_t, int);	bool may_pollfree;} __randomize_layout;

Common field analysis:

Function pointerUser-space callFunction
ownerUsually written asTHIS_MODULE, to prevent the module from being unloaded while in use.
openopen()Called when the device is opened, generally used for initialization or counting open times.
releaseclose()Called when the device is closed, used to release resources.
readread()User reads data from the device.
writewrite()User writes data to the device.
unlocked_ioctlioctl()User sends control commands.
pollpoll()/select()Implement non-blocking I/O (epoll mechanism)
mmapmmap()Map physical memory to user space.
llseeklseek()File position offset
fasyncfcntl(F_SETFL, O_ASYNC)Support asynchronous notification

Return value convention

  • ssize_t: >=0 success byte count, <0 errno
  • int/long: 0 on success, <0 errno
  • Pointer: success returns pointer, failure returnsERR_PTR(-errno)

llseek

1
loff_t (*llseek)(struct file *file, loff_t offset, int whence);
  • Function: File pointer offset operation (similar to lseek system call).
  • Parameters
    • file: File object.
    • offset: Offset.
    • whence
      • SEEK_SET: Relative to the beginning of the file
      • SEEK_CUR: Relative to the current file pointer
      • SEEK_END: Relative to the end of the file
  • Return value
    • New file pointer position (loff_t
    • On error, returns a negative value (e.g.-EINVAL

read

1
ssize_t (*read)(struct file *file, char __user *buf, size_t count, loff_t *pos);
  • Function: Read data from the file into user space.
  • Parameters
    • file: File object.
    • buf: User-space buffer, used to receive data read from the kernel.
    • count: Requested data transfer size (size of the user buffer).
    • pos: Indicates the starting position for reading data from the file; it needs to be updated after reading.
  • Return value
    • Success: actual number of bytes read
    • Error: negative errno

read steps

  1. Prevent the read operation from exceeding the file size, and return end-of-file:
12
if (*pos >= filesize)	return 0; /* 0 means EOF */
  1. The number of bytes read must not exceed the file size. Adjust count appropriately:
12
if (*pos + count > filesize)	count = filesize - (*pos);
  1. Find the starting position for reading:
1
void *from = pos_to_address (*pos); /* convert pos into valid address */
  1. Copy data to the user-space buffer, returning an error on failure:
123
sent = copy_to_user(buf, from, count);if (sent)	return -EFAULT;
  1. Advance the file’s current position by the number of bytes read, and return the number of bytes copied:
123
*pos += count;return count;

write

1
ssize_t (*write)(struct file *file, const char __user *buf, size_t count, loff_t *pos);
  • Function: Write user data to the file/device.
  • Parameters
    • file: file object
    • buf: user buffer
    • count: length of data requested to be transferred
    • pos: indicates the starting position in the file where the data should be written
  • Return value
    • Success: number of bytes actually written
    • Error: negative errno

write steps

  1. Check for errors or invalid requests from user space. This step is only meaningful if the device provides memory (EEPROM, I/O memory, etc.), which may have memory size limits:
123
/* If an attempt is made to write beyond the end of the file, return an error. Here filesize corresponds to the size of the device memory (if any). */if ( *pos >= filesize ) return -EINVAL;
  1. Adjust count for the remaining bytes so as not to exceed the file size. This step is also not required, related to the same condition as in step (1):
123
/* The file size is forced to match the device memory size. */if (*pos + count > filesize)	count = filesize - *pos;
  1. Find the position where writing begins. This step is only relevant when the device has memory and the write() method is to write the specified data into it. Like step (2), this step is not required:
12
/* Convert pos to a valid address*/void *from = pos_to_address( *pos );
  1. Copy data from user space and write it to the corresponding kernel space:
123456
if (copy_from_user(dev->buffer, buf, count) != 0){	retval = -EFAULT;	goto out;}/* Now move the data from dev->buffer to the physical device */
  1. Write to the physical device, returning an error on failure:
1234
write_error = device_write(dev->buffer, count);if ( write_error )	return -EFAULT;
  1. Move the current position of the file cursor by the number of bytes written. Finally, return the number of bytes copied:
123
*pos += count;return count;

mmap

1
int (*mmap)(struct file *, struct vm_area_struct *);
  • Function: Support user-space memory mapping to the device.
  • Parameters
    • file: file object
    • vma: virtual memory area structure
  • Return value
    • 0 success
    • <0 error
  • mmap_supported_flags: Flags supported during mapping.

open

1
int (*open)(struct inode *inode, struct file *file);
  • Function: When user space callsopen()It is called when opening a device or file.
  • Parameters
    • inode: Points to the inode structure corresponding to the device, containing filesystem layer information and device number.
    • file: Points to the file object (struct file), which stores file state, offset,private_dataetc.
  • Return value
    • 0: Successfully opened
    • <0: On error, returns the corresponding errno (such as-EBUSYindicates device busy,-ENOMEMout of memory)
  • Typical Uses
    • Check whether the device is already in use.
    • Initializefile->private_datapointer
    • Increment the module reference count (THIS_MODULE

flush

1
int (*flush)(struct file *file, fl_owner_t id);
  • Function: Called when the last reference to the file descriptor is closed, used to flush pending data in the device buffer.
  • Parameters
    • file: file object
    • id: The owner identifier of the file handle, usually the process corresponding to the file descriptor.
  • Return value
    • 0: success
    • <0: error
  • Description
    • For most character device drivers,flushno special implementation is needed, just return 0.
    • In block devices or network devices,flushit may be used to commit cached data.

release

1
int (*release)(struct inode *inode, struct file *file);
  • Function: When user space callsclose()It is called when closing the device or file.
  • Parameters
    • inode: The inode corresponding to the device
    • file: file object
  • Return value
    • 0: Successfully closed
    • <0: error
  • Typical Uses
    • Release atopen()resources allocated when
    • Restore device state (e.g., mark as unoccupied)
    • Reduce module reference count

example

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
#include <linux/init.h>#include <linux/module.h>#include <linux/cdev.h>static int major = 0;module_param(major, int, S_IRUGO);MODULE_PARM_DESC(major, "cdev_test sample, major device number");static int minor = 0;module_param(minor, int, S_IRUGO);MODULE_PARM_DESC(minor, "cdev_test sample, minor device number");dev_t dev_num;struct cdev cdev;int cdev_test_open(struct inode *inode, struct file *file){	pr_info("cdev_test open");	return 0;}ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset){	pr_info("cdev_test read\n");	return 0;}ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t, loff_t *offset){	pr_info("cdev_test write\n");	return 0;}struct file_operations fops = {	.owner = THIS_MODULE,	.open = cdev_test_open,	.read = cdev_test_read,	.write = cdev_test_write,};static int __init cdev_test_init(void){        int err;        if (major) {                dev_num = MKDEV(major, minor);                err = register_chrdev_region(dev_num, 1, "cdev test dev num");                if (err < 0) {                        pr_err("register_chrdev_region error\n");                        goto chrdev_region_err;                }                pr_info("register_chrdev_region success\n");        } else {                err = alloc_chrdev_region(&dev_num, 0, 1, "cdev test dev num");                if (err < 0) {                        pr_err("alloc_chrdev_region error\n");                        goto chrdev_region_err;                }                pr_info("alloc_chrdev_region success\n");        }	pr_info("dev_num: major[%d] minor[%d]", MAJOR(dev_num), MINOR(dev_num));        cdev_init(&cdev, &fops);        pr_info("cdev_init success\n");        cdev.owner = THIS_MODULE; // Point owner to this module to prevent unloading the module during cdev operations        err = cdev_add(&cdev, dev_num, 1);        pr_info("cdev_add success\n");        if (err < 0) {                pr_err("cdev_add error\n");                goto cdev_add_err;        }        return 0;cdev_add_err:        unregister_chrdev_region(dev_num, 1);chrdev_region_err:        return err;}static void __exit cdev_test_exit(void){        cdev_del(&cdev);        pr_info("cdev is deleted\n");        unregister_chrdev_region(dev_num, 1);        pr_info("chrdev_region unregister success\n");}module_init(cdev_test_init);module_exit(cdev_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@163.com>");MODULE_DESCRIPTION("This is just a cdev test sample");

Device node

In the Linux operating system, everything is a file. ForFiles used for device access are called device nodes. By operating this “device file”, the program can operate the corresponding hardware.

1
fd = open("/dev/hello", O_RDWR);

This “device file” is the device node. Therefore, the Linux device node is a bridge between applications and drivers. Device nodes are created in the /dev directory.

12345678910111213141516
[zhaohang@cyberboy linux_driver_learning]$ ll /devcrw-------   4,15 root 11 Nov 10:05  tty15crw-------   4,16 root 11 Nov 10:05  tty16crw-------   4,17 root 11 Nov 10:05  tty17crw-------   4,18 root 11 Nov 10:05  tty18crw-------   4,19 root 11 Nov 10:05  tty19crw-------   4,20 root 11 Nov 10:05  tty20crw-------   4,21 root 11 Nov 10:05  tty21crw-------   4,22 root 11 Nov 10:05  tty22crw-------   4,23 root 11 Nov 10:05  tty23crw-------   4,24 root 11 Nov 10:05  tty24crw-------   4,25 root 11 Nov 10:05  tty25crw-------   4,26 root 11 Nov 10:05  tty26crw-------   4,27 root 11 Nov 10:05  tty27crw-------   4,28 root 11 Nov 10:05  tty28crw-------   4,29 root 11 Nov 10:05  tty29

4,15Indicates the major device number and minor device number. In crw, c indicates a character device.

According to the different creation methods of device nodes, they are divided intoManually creating device nodesandAutomatically creating device nodes

Ways to create nodes in Linux

Manually creating device nodes

Use the mknod command to manually create device nodes. The mknod command format is:

1
$ mknod NAME TYPE MAJOR MINOR

Parameter meaning:

  • NAME: The name of the node to create
  • TYPE: b indicates a block device, c indicates a character device, p indicates a pipe
  • MAJOR: The major device number of the device to link
  • MINOR: The minor device number of the device to link

For example: Use the following command to create a character device node named device_test, with the device’s major and minor device numbers being 236 and 0 respectively.

1
$ mknod /dev/device_test c 236 0

For the above file_operations example:

123456789101112
$ insmod cdev_test.ko[   43.969734] cdev_test: loading out-of-tree module taints kernel.[   43.979783] alloc_chrdev_region success[   43.980242] dev_num: major[241] minor[0][   43.980277] cdev_init success[   43.981192] cdev_add success$ mknod /dev/cdev_test c 241 0$ ls /dev/cdev_test/dev/cdev_test$ cat /dev/cdev_test[   68.723315] cdev_test open[   68.723900] cdev_test read

Automatic device node creation

Automatic device node creation is achieved using udev the mechanism.

udev is a user program, by detecting the hardware device status in the system, it cancreate or delete device files according to the hardware device status in the system.

Automatically creating device nodesIn the driver, you first need to use the class_create() function to create a class, and the created class is located in the /sys/class/ directory.Then use the device_create() function to create the corresponding device under this class., when the driver module is loaded, udev in user space will automatically respond and create device nodes based on the information under /sys/class/.

In embedded Linux, we use mdev,mdev is a simplified version of udev. When using a root filesystem built with busybox, busybox will automatically create mdev.

class_create function

linux/device/class.h

Use the header file linux/device/device.h because device.h references class.h.

12345678
/* This is a #define to keep the compiler from merging different * instances of the __key variable */#define class_create(owner, name)		\({						\	static struct lock_class_key __key;	\	__class_create(owner, name, &__key);	\})
  • Function purpose: used to dynamically create a device class.
  • Parameter meaning
    • owner: A pointer to struct module type. Generally assigned THIS_MODULE.
    • name: A char pointer, representing the name of the struct class variable to be created.
  • Return valuestruct class *structure of type
class_destroy function

linux/device/class.h

1
extern void class_destroy(struct class *cls);
  • Function purpose: used to delete a device class.
  • Parameter meaning:
    • cls: callclass_create()returned by the functionstruct classpointer to the structure.
device_create function

linux/device/device.h

use class_After create creates the class, you also need to use device_The create function creates a device under the class.

123
__printf(5, 6) struct device *device_create(struct class *cls, struct device *parent, dev_t devt,	      void *drvdata, const char *fmt, ...);
  • Function purpose: used to create a device file under the class.
  • Parameter meaning:
    • cls: Specifies the class to which the device to be created belongs.
    • parent: Specifies the parent device of the device, or NULL if there is none.
    • devt: Specifies the device number for creating the device.
    • drvdata: Data passed to the device callback, or NULL if none.
    • fmt: The device node name added to the system.
  • Return value:struct device *Type structure
device_destroy function

linux/device/device.h

1
void device_destroy(struct class *cls, dev_t devt);
  • Function purpose: Used to delete a device in the class.
  • Parameter meaning:
    • cls: Specifies the class to which the device to be created belongs.
    • devt: Specifies the device number for creating the device.

example

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
#include <linux/init.h>#include <linux/module.h>#include <linux/cdev.h>static int major = 0;module_param(major, int, S_IRUGO);MODULE_PARM_DESC(major, "mknod_test dev major num");static int minor = 0;module_param(minor, int, S_IRUGO);MODULE_PARM_DESC(minor, "mknod_test dev minor num");dev_t dev_num;int mknod_test_open(struct inode *inode, struct file *file){        pr_info("This is mknod_test open\n");        return 0;}ssize_t mknod_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset){        pr_info("This is mknod test read\n");        return 0;}int mknod_test_release (struct inode *inode, struct file *file){        pr_info("This is mknod test release\n");        return 0;}struct file_operations fops = {        .owner = THIS_MODULE,        .open = mknod_test_open,        .read = mknod_test_read,        .release = mknod_test_release,};struct cdev cdev;struct class *class;struct device *dev;static int __init mknod_test_init(void){        int err;        pr_info("mknod_test module init\n");        if (major) {                dev_num = MKDEV(major, minor);                err = register_chrdev_region(dev_num, 1, "mknod test device num");                if (err < 0) {                        pr_err("register_chrdev_region error\n");                        goto chrdev_region_err;                }                pr_info("register_chrdev_region success\n");        } else {                err = alloc_chrdev_region(&dev_num, 0, 1, "mknod test device num");                if (err < 0) {                        pr_err("alloc_chrdev_region error\n");                        goto chrdev_region_err;                }                pr_info("alloc_chrdev_region success\n");        }        pr_info("dev_t dev_num: major[%d], minor[%d]\n", MAJOR(dev_num), MINOR(dev_num));        cdev_init(&cdev, &fops);        cdev.owner = THIS_MODULE;        err = cdev_add(&cdev, dev_num, 1);        if (err < 0) {                pr_err("cdev add error\n");                goto cdev_add_err;        }        class = class_create(THIS_MODULE, "chrdev");        if (IS_ERR(class)) {                err = PTR_ERR(class);                pr_err("class_create error\n");                goto class_create_err;        }        dev = device_create(class, NULL, dev_num, NULL, "mknod_test_device");        if(IS_ERR(dev)){                err = PTR_ERR(dev);                pr_err("device create error\n");                goto device_create_err;        }        return 0; device_create_err:        class_destroy(class);class_create_err:        cdev_del(&cdev);cdev_add_err:        unregister_chrdev_region(dev_num, 1);chrdev_region_err:        return err;}static void __exit mknod_test_exit(void){        device_destroy(class, dev_num);        class_destroy(class);        cdev_del(&cdev);        unregister_chrdev_region(dev_num, 1);        pr_info("mknod_test module exit\n");}module_init(mknod_test_init);module_exit(mknod_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@163.com>");MODULE_DESCRIPTION("This is just mknod test sample");

Test:

12345678910111213
$ insmod mknod_test.ko[   11.793662] mknod_test: loading out-of-tree module taints kernel.[   11.803110] mknod_test module init[   11.803371] alloc_chrdev_region success[   11.804103] dev_t dev_num: major[241], minor[0]$ ls /dev/mknod_test_device/dev/mknod_test_device$ cat /dev/mknod_test_device[   22.546620] This is mknod_test open[   22.547827] This is mknod test read[   22.548435] This is mknod test release$ rmmod mknod_test.ko[   28.103036] mknod_test module exit

Data exchange between user space and kernel space

The memory in kernel space and user space cannot access each other. However, many applications need to exchange data with the kernel, for example, applications useread()function to read data from the driver, and usewrite()function to write data to the driver. The above functionality requires the use ofcopy_from_user()andcopy_to_user()two functions to complete.

  • copy_from_user()The function copies data from user space to kernel space.
  • copy_to_user()The function copies data from kernel space to user space.

Common ways for kernel space to obtain user space data:

  • System calls (e.g., read/write)
  • Soft interrupts (e.g., socket communication)

include/linux/uaccess.h

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
/* * Architectures should provide two primitives (raw_copy_{to,from}_user()) * and get rid of their private instances of copy_{to,from}_user() and * __copy_{to,from}_user{,_inatomic}(). * * raw_copy_{to,from}_user(to, from, size) should copy up to size bytes and * return the amount left to copy.  They should assume that access_ok() has * already been checked (and succeeded); they should *not* zero-pad anything. * No KASAN or object size checks either - those belong here. * * Both of these functions should attempt to copy size bytes starting at from * into the area starting at to.  They must not fetch or store anything * outside of those areas.  Return value must be between 0 (everything * copied successfully) and size (nothing copied). * * If raw_copy_{to,from}_user(to, from, size) returns N, size - N bytes starting * at to must become equal to the bytes fetched from the corresponding area * starting at from.  All data past to + size - N must be left unmodified. * * If copying succeeds, the return value must be 0.  If some data cannot be * fetched, it is permitted to copy less than had been fetched; the only * hard requirement is that not storing anything at all (i.e. returning size) * should happen only when nothing could be copied.  In other words, you don't * have to squeeze as much as possible - it is allowed, but not necessary. * * For raw_copy_from_user() to always points to kernel memory and no faults * on store should happen.  Interpretation of from is affected by set_fs(). * For raw_copy_to_user() it's the other way round. * * Both can be inlined - it's up to architectures whether it wants to bother * with that.  They should not be used directly; they are used to implement * the 6 functions (copy_{to,from}_user(), __copy_{to,from}_user_inatomic()) * that are used instead.  Out of those, __... ones are inlined.  Plain * copy_{to,from}_user() might or might not be inlined.  If you want them * inlined, have asm/uaccess.h define INLINE_COPY_{TO,FROM}_USER. * * NOTE: only copy_from_user() zero-pads the destination in case of short copy. * Neither __copy_from_user() nor __copy_from_user_inatomic() zero anything * at all; their callers absolutely must check the return value. * * Biarch ones should also provide raw_copy_in_user() - similar to the above, * but both source and destination are __user pointers (affected by set_fs() * as usual) and both source and destination can trigger faults. */static __always_inline __must_check unsigned long__copy_from_user_inatomic(void *to, const void __user *from, unsigned long n){	instrument_copy_from_user(to, from, n);	check_object_size(to, n, false);	return raw_copy_from_user(to, from, n);}static __always_inline __must_check unsigned long__copy_from_user(void *to, const void __user *from, unsigned long n){	might_fault();	if (should_fail_usercopy())		return n;	instrument_copy_from_user(to, from, n);	check_object_size(to, n, false);	return raw_copy_from_user(to, from, n);}/** * __copy_to_user_inatomic: - Copy a block of data into user space, with less checking. * @to:   Destination address, in user space. * @from: Source address, in kernel space. * @n:    Number of bytes to copy. * * Context: User context only. * * Copy data from kernel space to user space.  Caller must check * the specified block with access_ok() before calling this function. * The caller should also make sure he pins the user space address * so that we don't result in page fault and sleep. */static __always_inline __must_check unsigned long__copy_to_user_inatomic(void __user *to, const void *from, unsigned long n){	if (should_fail_usercopy())		return n;	instrument_copy_to_user(to, from, n);	check_object_size(from, n, true);	return raw_copy_to_user(to, from, n);}static __always_inline __must_check unsigned long__copy_to_user(void __user *to, const void *from, unsigned long n){	might_fault();	if (should_fail_usercopy())		return n;	instrument_copy_to_user(to, from, n);	check_object_size(from, n, true);	return raw_copy_to_user(to, from, n);}#ifdef INLINE_COPY_FROM_USERstatic inline __must_check unsigned long_copy_from_user(void *to, const void __user *from, unsigned long n){	unsigned long res = n;	might_fault();	if (!should_fail_usercopy() && likely(access_ok(from, n))) {		instrument_copy_from_user(to, from, n);		res = raw_copy_from_user(to, from, n);	}	if (unlikely(res))		memset(to + (n - res), 0, res);	return res;}#elseextern __must_check unsigned long_copy_from_user(void *, const void __user *, unsigned long);#endif#ifdef INLINE_COPY_TO_USERstatic inline __must_check unsigned long_copy_to_user(void __user *to, const void *from, unsigned long n){	might_fault();	if (should_fail_usercopy())		return n;	if (access_ok(to, n)) {		instrument_copy_to_user(to, from, n);		n = raw_copy_to_user(to, from, n);	}	return n;}#elseextern __must_check unsigned long_copy_to_user(void __user *, const void *, unsigned long);#endifstatic __always_inline unsigned long __must_checkcopy_from_user(void *to, const void __user *from, unsigned long n){	if (likely(check_copy_size(to, n, false)))		n = _copy_from_user(to, from, n);	return n;}static __always_inline unsigned long __must_checkcopy_to_user(void __user *to, const void *from, unsigned long n){	if (likely(check_copy_size(from, n, true)))		n = _copy_to_user(to, from, n);	return n;}#ifdef CONFIG_COMPATstatic __always_inline unsigned long __must_checkcopy_in_user(void __user *to, const void __user *from, unsigned long n){	might_fault();	if (access_ok(to, n) && access_ok(from, n))		n = raw_copy_in_user(to, from, n);	return n;}#endif#ifndef copy_mc_to_kernel/* * Without arch opt-in this generic copy_mc_to_kernel() will not handle * #MC (or arch equivalent) during source read. */static inline unsigned long __must_checkcopy_mc_to_kernel(void *dst, const void *src, size_t cnt){	memcpy(dst, src, cnt);	return 0;}#endif

The overall call chain is as follows

123456789
copy_from_user(to, from, n) └─ check_copy_size(to, n, false)   // Compile-time object size checking (FORTIFY_SOURCE)    └─ _copy_from_user(to, from, n)       ├─ might_fault()               // Mark as potentially causing page faults (scheduling points)       ├─ should_fail_usercopy()      // Fault injection (for testing)       ├─ access_ok(from, n)          // Check whether the user pointer is valid (critical!)       ├─ instrument_copy_from_user() // KASAN / UBSAN detection       ├─ raw_copy_from_user(...)     // Actual copy (arch-specific)       └─ if (res) memset(..., 0, res) // **Zero-padding!**
  • Zero-padding: If only part of the data is copied (e.g., due to a page fault),the remaining uncopied portion is zeroed. This is to prevent kernel information leakage (e.g., uninitialized memory on the stack being read by the user).

  • access_ok(): ensurefrompoints to user space and the length is valid (prevents access to kernel addresses)

  • check_object_size(): prevents buffer overflow (in conjunction with FORTIFY_SOURCE)

  • instrument_*: KASAN detects out-of-bounds access

Even if the copy fails, it does not panic; instead, it returns the number of uncopied bytes, which the driver can handle accordingly.

copy_to_user()

Function prototype:

1234567
static __always_inline unsigned long __must_checkcopy_to_user(void __user *to, const void *from, unsigned long n){	if (likely(check_copy_size(from, n, true)))		n = _copy_to_user(to, from, n);	return n;}
  • Function purpose: copy data from kernel space to user space.
  • Parameter meaning:
    • *toUser space pointer.
    • fromKernel space pointer.
    • nis the number of bytes copied from kernel space to user space
  • Return value: 0 means success, others mean failure.

copy_from_user()

1234567
static __always_inline unsigned long __must_checkcopy_from_user(void *to, const void __user *from, unsigned long n){	if (likely(check_copy_size(to, n, false)))		n = _copy_from_user(to, from, n);	return n;}
  • Function: copies data from user space to kernel space.
  • Parameter meaning
    • *toKernel space pointer.
    • fromUser space pointer.
    • nNumber of bytes copied from user space to kernel space.
  • Return value: 0 means success, others mean failure.

copy_struct_from_user()

This is the recommended interface for modern syscalls.

Problem solvedThe version of the struct passed from user space differs.

  • If the struct passed by the user is smaller, the kernel automatically pads with zeros.
  • If the struct passed by the user is larger, check whether the extra fields are zero.

Return value

  • 0Success
  • -EFAULTAccess failure.
  • -E2BIGNon-zero extended fields.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
/** * copy_struct_from_user: copy a struct from userspace * @dst:   Destination address, in kernel space. This buffer must be @ksize *         bytes long. * @ksize: Size of @dst struct. * @src:   Source address, in userspace. * @usize: (Alleged) size of @src struct. * * Copies a struct from userspace to kernel space, in a way that guarantees * backwards-compatibility for struct syscall arguments (as long as future * struct extensions are made such that all new fields are *appended* to the * old struct, and zeroed-out new fields have the same meaning as the old * struct). * * @ksize is just sizeof(*dst), and @usize should've been passed by userspace. * The recommended usage is something like the following: * *   SYSCALL_DEFINE2(foobar, const struct foo __user *, uarg, size_t, usize) *   { *      int err; *      struct foo karg = {}; * *      if (usize > PAGE_SIZE) *        return -E2BIG; *      if (usize < FOO_SIZE_VER0) *        return -EINVAL; * *      err = copy_struct_from_user(&karg, sizeof(karg), uarg, usize); *      if (err) *        return err; * *      // ... *   } * * There are three cases to consider: *  * If @usize == @ksize, then it's copied verbatim. *  * If @usize < @ksize, then the userspace has passed an old struct to a *    newer kernel. The rest of the trailing bytes in @dst (@ksize - @usize) *    are to be zero-filled. *  * If @usize > @ksize, then the userspace has passed a new struct to an *    older kernel. The trailing bytes unknown to the kernel (@usize - @ksize) *    are checked to ensure they are zeroed, otherwise -E2BIG is returned. * * Returns (in all cases, some data may have been copied): *  * -E2BIG:  (@usize > @ksize) and there are non-zero trailing bytes in @src. *  * -EFAULT: access to userspace failed. */static __always_inline __must_check intcopy_struct_from_user(void *dst, size_t ksize, const void __user *src,		      size_t usize){	size_t size = min(ksize, usize);	size_t rest = max(ksize, usize) - size;	/* Deal with trailing bytes. */	if (usize < ksize) {		memset(dst + size, 0, rest);	} else if (usize > ksize) {		int ret = check_zeroed_user(src + size, rest);		if (ret <= 0)			return ret ?: -E2BIG;	}	/* Copy the interoperable parts of the struct. */	if (copy_from_user(dst, src, size))		return -EFAULT;	return 0;}

No copy._struct_The to_user function.

get_user()

123456789
// include/asm-generic/uaccess.h#define get_user(x, ptr)					\({								\	const void __user *__p = (ptr);				\	might_fault();						\	access_ok(__p, sizeof(*ptr)) ?		\		__get_user((x), (__typeof__(*(ptr)) __user *)__p) :\		((x) = (__typeof__(*(ptr)))0,-EFAULT);		\})

Reads a simple variable, handles faults internally. The macro copies the value of a user-space variable to kernel space. Note: x is set to 0 on error.

  • x is the kernel variable that stores the result.
  • ptr is the source address in user space.

The result of dereferencing ptr must be assignable to x without a cast.

Returns 0 on success, and an error code on failure.-EFAULT

put_user()

12345678910
// include/asm-generic/uaccess.h#define put_user(x, ptr)					\({								\	void __user *__p = (ptr);				\	might_fault();						\	access_ok(__p, sizeof(*ptr)) ?		\		__put_user((x), ((__typeof__(*(ptr)) __user *)__p)) :	\		-EFAULT;					\})

This macro copies a kernel-space variable value to user space.

  • x is the value to be copied to user space.
  • ptr is the destination address in user space.

x must be assignable to the result of dereferencing ptr. In other words, they must have (or point to) the same type.

Returns 0 on success, and returns on error-EFAULT

example

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
#include <linux/printk.h>#include <linux/init.h>#include <linux/module.h>#include <linux/kernel.h>#include <linux/moduleparam.h>#include <linux/fs.h>#include <linux/types.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/uaccess.h>static int major = 0;static int minor = 0;module_param(major, int, S_IRUGO);module_param(minor, int, S_IRUGO);struct class *class;struct device *device;static dev_t dev_id;static struct cdev cdev_test;int cdev_test_open(struct inode *inode, struct file *file){	pr_info("cdev_test_open was called");	return 0;}ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size,		       loff_t *off){	char kbuf[] = "This is my_driver read\n";	if (copy_to_user(buf, kbuf, strlen(kbuf)) != 0) {		pr_err("copy kbuf to buf from kernel error\n");		return -1;	}	pr_info("copy kbuf to buf from kernel ok\n");	pr_info("cdev_test_read was called");	return 0;}ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size,			loff_t *off){	char kbuf[32] = { 0 };	if (copy_from_user(kbuf, buf, size) != 0) {		pr_err("copy buf to kbuf from kernel error\n");		return -1;	}	printk("read from user: %s", kbuf);	pr_info("cdev_test_write was called");	return 0;}int cdev_test_release(struct inode *inode, struct file *file){	pr_info("cdev_test_release was called");	return 0;}static struct file_operations cdev_test_ops = { .owner = THIS_MODULE,						.open = cdev_test_open,						.read = cdev_test_read,						.write = cdev_test_write,						.release = cdev_test_release };static int __init my_driver_init(void){	int ret;	if (major) {		dev_id = MKDEV(major, minor);		pr_info("major from module_param: %d", MAJOR(dev_id));		pr_info("minor from module_param: %d", MINOR(dev_id));		ret = register_chrdev_region(dev_id, 1, "my_driver device");		if (ret < 0) {			pr_err("register_chrdev_region error\n");			return ret;		} else {			pr_info("register_chrdev_region ok\n");		}	} else {		ret = alloc_chrdev_region(&dev_id, 0, 1, "my_driver device");		if (ret < 0) {			pr_err("alloc_chrdev_region error\n");			return ret;		} else {			pr_info("alloc_chrdev_region ok\n");			pr_info("major allocated: %d", MAJOR(dev_id));			pr_info("minor allocated: %d", MINOR(dev_id));		}	}	cdev_init(&cdev_test, &cdev_test_ops);	//Pointing the owner field to this module can prevent the module from being unloaded while its operations are in use.	cdev_test.owner = THIS_MODULE;	ret = cdev_add(&cdev_test, dev_id, 1);	if (ret < 0) {		pr_err("cdev_add error\n");		unregister_chrdev_region(dev_id, 1);		return ret;	} else {		pr_info("cdev_add ok\n");	}	class = class_create(THIS_MODULE, "test");	// /dev/my_driver	device = device_create(class, NULL, dev_id, NULL, "my_driver");	pr_info("my_driver: Module loaded\n");	return 0;}static void __exit my_driver_exit(void){	// Delete device node	device_destroy(class, dev_id);	// Delete the class of this device node	class_destroy(class);	// Delete cdev	cdev_del(&cdev_test);	// Release device number	unregister_chrdev_region(dev_id, 1);	pr_info("unregister_chrdev_region ok\n");	pr_info("my_driver: Module unloaded\n");}module_init(my_driver_init);module_exit(my_driver_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Zhao Hang");MODULE_DESCRIPTION("my_driver Kernel Module");

Test

Test code:

12345678910111213141516171819202122232425262728293031323334353637
#include <stdio.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>#include <unistd.h>int main(int argc, char **argv){	int ret, fd;	fd = open("/dev/my_driver", O_RDWR);	if (fd < 0) {		printf("open /dev/my_driver error\n");		return -1;	}	char rbuf[32] = { 0 };	ret = read(fd, rbuf, sizeof(rbuf));	if (ret < 0) {		printf("read error\n");		goto err;	}	printf("read from driver:%s\n", rbuf);	char wbuf[32] = "hello world\n";	ret = write(fd, wbuf, sizeof(wbuf));	if (ret < 0) {		printf("write error\n");		goto err;	}success:	close(fd);	return 0;err:	close(fd);	return -1;}

makefile

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
TARGET_EXEC := testSRC_DIRS := ./BUILD_DIR := ./buildCC = aarch64-linux-gnu-gccCFLAGS = -g -Wall# Source file collectionSRCS := $(shell find $(SRC_DIRS) -name '*.c' -or -name '*.s' -or -name '*.S')# Object file mappingOBJS := $(patsubst %.c,$(BUILD_DIR)/%.o,$(SRCS))OBJS := $(patsubst %.s,$(BUILD_DIR)/%.o,$(OBJS))OBJS := $(patsubst %.S,$(BUILD_DIR)/%.o,$(OBJS))# Include pathINC_DIRS := $(shell find $(SRC_DIRS) -type d)INC_FLAGS := $(addprefix -I,$(INC_DIRS))# Automatically generate dependency filesCPPFLAGS := $(INC_FLAGS) -MMD -MP# Link the final executable$(BUILD_DIR)/$(TARGET_EXEC): $(OBJS)	@mkdir -p $(dir $@)	$(CC) $(OBJS) -o $@ $(LDFLAGS)# Compilation rules (C)$(BUILD_DIR)/%.o: %.c	@mkdir -p $(dir $@)	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@# Compilation rules (Assembly)# Assembly code that does not require preprocessing$(BUILD_DIR)/%.o: %.s	@mkdir -p $(dir $@)	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@# Assembly code that requires preprocessing$(BUILD_DIR)/%.o: %.S	@mkdir -p $(dir $@)	$(CC) $(CPPFLAGS) $(CFLAGS) -c $< -o $@# Cleanup.PHONY: clean deployclean:	rm -rf $(BUILD_DIR)deploy:	cp $(BUILD_DIR)/$(TARGET_EXEC) ~/tftp# Automatic dependency inclusion-include $(OBJS:.o=.d)

Test output:

12345678910111213141516
~ # insmod my_driver.ko[    7.250701] my_driver: loading out-of-tree module taints kernel.[    7.257204] alloc_chrdev_region ok[    7.257387] major allocated: 511[    7.257406] minor allocated: 0[    7.257563] cdev_add ok[    7.258587] my_driver: Module loaded~ # mdev -s~ # ./test[   18.006803] cdev_test_open was called[   18.007016] copy kbuf to buf from kernel okread from driver:This is my_driver read[   18.007275] cdev_test_read was called[   18.010813] read from user: hello world[   18.011205] cdev_test_write was called

File private data

Usually in driver development, we willDefine a custom device structure for the deviceandSet the device structure as file private dataThe device structure is used to store hardware-related information, such as device number, class, device name, etc.

Linux does not explicitly require the use of file private dataHowever, file private data can be seen everywhere in Linux driver source code, so it can be considered that using file private data is an ‘unwritten rule’ followed by Linux drivers. In fact, file private data also reflects Linux’s object-oriented thinking.

File private data means pointing the private data private_data to the device structure.

123456
struct device_test dev1;static int cdev_test_open(struct inode *inode, struct file *file){    file->private_data = &dev1;    return 0;}

Then, in functions such as read and write, access the device structure through private_data.

1234
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off_t){    	struct device_test *test_dev = (struct device_test *)file->private_data;    	return 0;}

Global variables can also achieve this functionality, but as driver code complexity increases, using private_data is more manageable.

example

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
#include <linux/init.h>#include <linux/module.h>#include <linux/cdev.h>#include <linux/slab.h>#include <linux/uaccess.h>#include <linux/string.h>static int major = 0;module_param(major, int, S_IRUGO);MODULE_DESCRIPTION("device num: major");static int minor = 0;module_param(minor, int, S_IRUGO);MODULE_DESCRIPTION("device num: minor");static char buf[] = "Hello World from kernel\n";struct test_data {        dev_t dev_num;        struct cdev cdev;        struct class *class;        struct device *dev;        char kbuf[32];};static struct test_data *dat;static int file_private_test_open(struct inode *inode, struct file *file){        pr_info("file_private_test_open is called\n");        // Set private data        file->private_data = dat;        pr_info("file->private_data is set\n");        return 0;}static ssize_t file_private_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset){        struct test_data *data;        size_t len;        if (*offset != 0) { // EOF                return 0;        }        data = file->private_data;        len = min(size, strlen(dat->kbuf));        pr_info("file_private_test_read is called\n");        if (copy_to_user(buf, data->kbuf, len) != 0) {                pr_err("copy_to_user error\n");                return -EFAULT;        }        *offset += len;        return sizeof(data->kbuf);}static int file_private_test_release(struct inode *inode, struct file *file){        pr_info("file_private_test_release is called\n");        return 0;}static struct file_operations fops = {        .owner = THIS_MODULE,        .open = file_private_test_open,        .read = file_private_test_read,        .release = file_private_test_release,};static int __init file_private_test_init(void){        int err;        pr_info("file_private_test init\n");        dat = (struct test_data *)kmalloc(sizeof(struct test_data), GFP_KERNEL);        if (dat == NULL) {                pr_err("no memory\n");                goto kmalloc_fail;        }        strscpy(dat->kbuf, buf, sizeof(dat->kbuf));        // Allocate device number        if (major) {                dat->dev_num = MKDEV(major, minor);                err = register_chrdev_region(dat->dev_num, 1, "file_private_test chrdev region");                if (err < 0) {                        pr_err("register_chrdev_region error\n");                        goto chrdev_region_fail;                }        } else {                err = alloc_chrdev_region(&dat->dev_num, 0, 1, "file_private_test chrdev region");                if (err < 0) {                        pr_err("register_chrdev_region error\n");                        goto chrdev_region_fail;                }        }        // Add cdev        cdev_init(&dat->cdev, &fops);        err = cdev_add(&dat->cdev, dat->dev_num, 1);        if (err < 0) {                pr_err("cdev_add error\n");                goto cdev_add_fail;        }        // Create device node        dat->class = class_create(THIS_MODULE, "char_test");        if (IS_ERR(dat->class)) {                err = PTR_ERR(dat->class);                pr_err("create class error");                goto class_create_fail;        }        dat->dev = device_create(dat->class, NULL, dat->dev_num, NULL, "char_test_dev");        if (IS_ERR(dat->dev)) {                err = PTR_ERR(dat->dev);                pr_err("create device error\n");                goto device_create_fail;        }        return 0;device_create_fail:        class_destroy(dat->class);class_create_fail:        cdev_del(&dat->cdev);cdev_add_fail:        unregister_chrdev_region(dat->dev_num, 1);chrdev_region_fail:        kfree(dat);kmalloc_fail:        return err;}static void __exit file_private_test_exit(void){        device_destroy(dat->class, dat->dev_num);        class_destroy(dat->class);        cdev_del(&dat->cdev);        unregister_chrdev_region(dat->dev_num, 1);        kfree(dat);        pr_info("file_private_test exit\n");}module_init(file_private_test_init);module_exit(file_private_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@163.com>");MODULE_DESCRIPTION("This is a test sample for file private data");

Test:

123456789
$ insmod file_private_data_test.ko[    8.022277] file_private_data_test: loading out-of-tree module taints kernel.[    8.032581] file_private_test init$ cat /dev/char_test_dev[   14.998329] file_private_test_open is called[   14.998580] file->private_data is set[   14.999659] file_private_test_read is calledHello World from kernel[   15.000469] file_private_test_release is called

Scenarios for using file private data

In Linux, the major device number is used to represent a particular type of driver. The minor device number is used to represent individual devices under that type of driver.

Suppose our driver nowneeds to support devices with the same major device number but different minor device numbersOur driver can then be written using file private data.

container_of macro

include/linux/kernel.h

123456
#define container_of(ptr, type, member) ({				\	void *__mptr = (void *)(ptr);					\	BUILD_BUG_ON_MSG(!__same_type(*(ptr), ((type *)0)->member) &&	\			 !__same_type(*(ptr), void),			\			 "pointer type mismatch in container_of()");	\	((type *)(__mptr - offsetof(type, member))); })
  • ptrA pointer to a member within a structure.

  • typeThe target structure type.

  • memberThe name of the member in the structure.

  • BUILD_BUG_ON_MSG(...)Compile-time type checking ensures that ptr matches the type of the structure member, avoiding type errors.

  • __mptr - offsetof(type, member)Core formula: subtract the member’s offset within the structure from the member pointer to obtain the starting address of the structure.

  • offsetof(type, member)A standard macro that calculates the byte offset of a member within a structure.

example

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
#include <linux/module.h>#include <linux/init.h>#include <linux/cdev.h>#include <linux/slab.h>#include <linux/uaccess.h>#define KBUF_SIZE 32struct container_data {        dev_t dev_num;        struct cdev cdev;        struct device *dev;        char kbuf[KBUF_SIZE];};static struct class *class;static struct container_data *dat1;static struct container_data *dat2;int container_of_test_open(struct inode *inode, struct file *file){        struct container_data *dat;        // Through container_of to obtain a specific container_data        dat = container_of(inode->i_cdev, struct container_data, cdev);        file->private_data = dat;        pr_info("container_of_test_open is called, dev major: %d, dev minor: %d\n",                MAJOR(dat->dev_num), MINOR(dat->dev_num));        return 0;}ssize_t container_of_test_read(struct file *file, char __user *buf, size_t size, loff_t *offset){        struct container_data *dat = file->private_data;        size_t kbuf_len = strlen(dat->kbuf);        size_t len = min(size, (size_t)(kbuf_len - *offset));        if (*offset >= kbuf_len)                return 0;        if (copy_to_user(buf, dat->kbuf + *offset, len))                return -EFAULT;        *offset += len;        return len;}ssize_t container_of_test_write(struct file *file, const char __user *buf, size_t size,                                loff_t *offset){        struct container_data *dat = file->private_data;        size_t bytes = min(size, (size_t)(KBUF_SIZE - 1 - *offset));        if (*offset >= KBUF_SIZE - 1)                return -ENOSPC;        if (copy_from_user(dat->kbuf + *offset, buf, bytes) != 0)                return -EFAULT;        *offset += bytes;        dat->kbuf[*offset] = '\0';        return bytes;}int container_of_test_release(struct inode *inode, struct file *file){        pr_info("container_of_test_release is called\n");        return 0;}static struct file_operations fops = {        .owner = THIS_MODULE,        .open = container_of_test_open,        .read = container_of_test_read,        .write = container_of_test_write,        .release = container_of_test_release,};static int __init container_of_test_init(void){        int err;        dat1 = (struct container_data *)kmalloc(sizeof(struct container_data), GFP_KERNEL);        if (dat1 == NULL) {                pr_err("no memory dat1");                err = -ENOMEM;                goto kmalloc_dat1_fail;        }        memset(dat1, 0, sizeof(struct container_data));        dat2 = (struct container_data *)kmalloc(sizeof(struct container_data), GFP_KERNEL);        if (dat2 == NULL) {                pr_err("no memory dat2");                err = -ENOMEM;                goto kmalloc_dat2_fail;        }        memset(dat2, 0, sizeof(struct container_data));        err = alloc_chrdev_region(&dat1->dev_num, 0, 2, "container_of_test chrdev region");        if (err < 0) {                pr_err("alloc_chrdev_region error\n");                goto alloc_chrdev_region_fail;        }        dat2->dev_num = MKDEV(MAJOR(dat1->dev_num), MINOR(dat1->dev_num) + 1);        cdev_init(&dat1->cdev, &fops);        err = cdev_add(&dat1->cdev, dat1->dev_num, 1);        if (err < 0) {                pr_err("cdev_add dat1 error\n");                goto cdev_add_dat1_fail;        }        cdev_init(&dat2->cdev, &fops);        err = cdev_add(&dat2->cdev, dat2->dev_num, 1);        if (err < 0) {                pr_err("cdev_add dat2 error\n");                goto cdev_add_dat2_fail;        }        class = class_create(THIS_MODULE, "chrdev");        if (IS_ERR(class)) {                err = PTR_ERR(class);                goto class_create_fail;        }        dat1->dev = device_create(class, NULL, dat1->dev_num, NULL, "container_of_test_dev%d", 0);        if (IS_ERR(dat1->dev)) {                err = PTR_ERR(dat1->dev);                goto device_create_dat1_fail;        }        dat2->dev = device_create(class, NULL, dat2->dev_num, NULL, "container_of_test_dev%d", 1);        if (IS_ERR(dat2->dev)) {                err = PTR_ERR(dat2->dev);                goto device_create_dat2_fail;        }        return 0;device_create_dat2_fail:        device_destroy(class, dat1->dev_num);device_create_dat1_fail:        class_destroy(class);class_create_fail:        cdev_del(&dat2->cdev);cdev_add_dat2_fail:        cdev_del(&dat1->cdev);cdev_add_dat1_fail:        unregister_chrdev_region(dat1->dev_num, 2);alloc_chrdev_region_fail:        kfree(dat2);kmalloc_dat2_fail:        kfree(dat1);kmalloc_dat1_fail:        return err;}static void __exit container_of_test_exit(void){        device_destroy(class, dat1->dev_num);        device_destroy(class, dat2->dev_num);        class_destroy(class);        cdev_del(&dat2->cdev);        cdev_del(&dat1->cdev);        unregister_chrdev_region(dat1->dev_num, 2);        kfree(dat2);        kfree(dat1);        pr_info("container_of_test exit\n");}module_init(container_of_test_init);module_exit(container_of_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@163.com>");MODULE_DESCRIPTION("This is a test sample for container_of");

Miscellaneous device driver

Miscellaneous devices belong toa special type of character deviceIt is a wrapper for character devices, and in essence is also a character device.

In Linux, devices that cannot be categorized are defined as miscellaneous devices. Compared to character devices, miscellaneous devices have the following two advantages:

  1. Saves major device numbers: The major device number for miscellaneous devices is fixed at 10When multiple miscellaneous device drivers are registered in the system, they only need to be distinguished by their minor device numbers. Character devices, however, consume a major device number whether the device number is allocated dynamically or statically, resulting in a waste of major device numbers.
  2. Easy to use:**The misc device driver itself includes the operation of creating device nodes, so there is no need to additionally use the class_create() function and device_create() function.**It implements automatic device node creation. You only need to fill in the members of the file_operations structure in the driver.

Definition

include/linux/miscdevice.h

1234567891011121314151617
struct device;struct attribute_group;struct miscdevice  {	int minor;	const char *name;	const struct file_operations *fops;	struct list_head list;	struct device *parent;	struct device *this_device;	const struct attribute_group **groups;	const char *nodename;	umode_t mode;};extern int misc_register(struct miscdevice *misc);extern void misc_deregister(struct miscdevice *misc);

To define a misc device, generally you only need to fill in the three member variables: minor, name, and fops in the miscdevice structure.

  • minorThe minor device number of the misc device can be selected from the kernel source codeinclude/linux/miscdevice.hfrom the predefined minor device numbers in the file, or you can define your own minor device number (as long as it is not used by other devices). Usually this parameter is set to MISC_DYNAMIC_MINOR, which means the minor device number is automatically allocated.
  • nameRepresents the name of the misc device. That is, after the misc device driver is successfully registered, a device node named name will be generated in the /dev directory.
  • fopsPoints to the file_operations structure, representing the file operation set.

misc_register

1
extern int misc_register(struct miscdevice *misc);
  • Function purpose: Based on misc_class, construct a device, and mount the miscdevice structure to the misc_list, and initialize the structures related to the Linux device model. This serves to register the misc device.

  • Parameter meaning

    • misc: Pointer to the misc device structure
  • Function return value: Returns 0 on success, and a negative number on failure.

misc_deregister

1
extern void misc_deregister(struct miscdevice *misc);
  • Function purpose: Removes the miscdevice from the misc_list, thereby serving to unload the misc device.
  • Parameter meaning
    • misc: Pointer to the misc device structure

example

123456789101112131415161718192021222324252627282930
#include <linux/init.h>#include <linux/module.h>#include <linux/miscdevice.h>static struct file_operations fops = {        .owner = THIS_MODULE,};static struct miscdevice miscdev={        .minor=MISC_DYNAMIC_MINOR,// Dynamically allocate a minor device number        .name="miscdev",        .fops = &fops,};static int __init misc_device_test_init(void){        misc_register(&miscdev);        return 0;}static void __exit misc_device_test_exit(void){        misc_deregister(&miscdev);}module_init(misc_device_test_init);module_exit(misc_device_test_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@163.com>");MODULE_DESCRIPTION("This is a misc device sample");

Linux driver error handling

goto reverse-order release

Reverse-order release: the one requested later is released first

123456789101112131415161718192021222324252627282930313233343536373839
static int __init my_driver_init(void){    int ret;    ret = alloc_chrdev_region(&dev1.dev_num, 0, 2, "my_driver");    if (ret < 0)        goto fail_alloc;    cdev_init(&dev1.cdev_test, &cdev_test_fops);    dev1.cdev_test.owner = THIS_MODULE;    ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);    if (ret < 0)        goto fail_cdev_add;    dev1.class = class_create(THIS_MODULE, "test1");    if (IS_ERR(dev1.class)) {        ret = PTR_ERR(dev1.class);        goto fail_class_create;    }    dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test1");    if (IS_ERR(dev1.device)) {        ret = PTR_ERR(dev1.device);        goto fail_device_create;    }    pr_info("my_driver initialized successfully\n");    return 0;fail_device_create:    class_destroy(dev1.class);fail_class_create:    cdev_del(&dev1.cdev_test);fail_cdev_add:    unregister_chrdev_region(dev1.dev_num, 1);fail_alloc:    return ret;}
  • The label order is “reverse release”, that is, the one requested later is released first
  • PTR_ERR()+IS_ERR()used to determineclass_create()anddevice_create()the error.
  • PTR_ERR()Convert the error pointer to an error code, and return the error code.
  • gotocan avoid writing the same code multiple timesifcheck and clean up the code, making the code concise.

IS_ERR()

If a function’s return value is a pointer type, it will return a NULL pointer when the call fails, but returning NULL cannot tell the exact nature of the problem. Some functions need to return an actual error code so that engineers can make correct judgments based on the return value.
For any pointer, there are necessarily three cases:

  • valid pointer
  • NULL (that is, a null pointer)
  • error pointer (that is, an invalid pointer)

In the Linux kernel, the so-callederror pointer points to the last page of kernel space, for example, for a 64-bit system, the address of the last page is 0xfffffffffffff000~0xffffffffffffffff,
The last page address is reserved, and this address range is associated with the error codes defined by the kernel. Therefore, error codes can be used to indicate the corresponding error conditions. If a pointer points to the address range of this page, it is defined as an error pointer.
In the Linux kernel source code, API functions related to error pointers are provided, mainly including IS_ERR()、PTR_ERR(), ERR_PTR() functions, etc., whose source code is ininclude/linux/err.hfile.

include/linux/err.h

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
/* SPDX-License-Identifier: GPL-2.0 */#ifndef _LINUX_ERR_H#define _LINUX_ERR_H#include <linux/compiler.h>#include <linux/types.h>#include <asm/errno.h>/* * Kernel pointers have redundant information, so we can use a * scheme where we can return either an error code or a normal * pointer with the same return value. * * This should be a per-architecture thing, to allow different * error and pointer decisions. */#define MAX_ERRNO	4095#ifndef __ASSEMBLY__#define IS_ERR_VALUE(x) unlikely((unsigned long)(void *)(x) >= (unsigned long)-MAX_ERRNO)static inline void * __must_check ERR_PTR(long error){	return (void *) error;}static inline long __must_check PTR_ERR(__force const void *ptr){	return (long) ptr;}static inline bool __must_check IS_ERR(__force const void *ptr){	return IS_ERR_VALUE((unsigned long)ptr);}static inline bool __must_check IS_ERR_OR_NULL(__force const void *ptr){	return unlikely(!ptr) || IS_ERR_VALUE((unsigned long)ptr);}/** * ERR_CAST - Explicitly cast an error-valued pointer to another pointer type * @ptr: The pointer to cast. * * Explicitly cast an error-valued pointer to another pointer type in such a * way as to make it clear that's what's going on. */static inline void * __must_check ERR_CAST(__force const void *ptr){	/* cast away the const */	return (void *) ptr;}static inline int __must_check PTR_ERR_OR_ZERO(__force const void *ptr){	if (IS_ERR(ptr))		return PTR_ERR(ptr);	else		return 0;}#endif#endif /* _LINUX_ERR_H */

PTR_ERR()

Convert the error pointer into an error code.

include/linux/err.h

1234
static inline long __must_check PTR_ERR(__force const void *ptr){	return (long) ptr;}

ERR_PTR()

Convert the error code into a pointer.

1234
static inline void * __must_check ERR_PTR(long error){	return (void *) error;}

IS_ERR_OR_NULL()

Determine whether it is an error or NULL.

1234
static inline bool __must_check IS_ERR_OR_NULL(__force const void *ptr){	return unlikely(!ptr) || IS_ERR_VALUE((unsigned long)ptr);}

error code

include/uapi/asm-generic/errno-base.h

1234567891011121314151617181920212223242526272829303132333435363738394041
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */#ifndef _ASM_GENERIC_ERRNO_BASE_H#define _ASM_GENERIC_ERRNO_BASE_H#define	EPERM		 1	/* Operation not permitted */#define	ENOENT		 2	/* No such file or directory */#define	ESRCH		 3	/* No such process */#define	EINTR		 4	/* Interrupted system call */#define	EIO		 5	/* I/O error */#define	ENXIO		 6	/* No such device or address */#define	E2BIG		 7	/* Argument list too long */#define	ENOEXEC		 8	/* Exec format error */#define	EBADF		 9	/* Bad file number */#define	ECHILD		10	/* No child processes */#define	EAGAIN		11	/* Try again */#define	ENOMEM		12	/* Out of memory */#define	EACCES		13	/* Permission denied */#define	EFAULT		14	/* Bad address */#define	ENOTBLK		15	/* Block device required */#define	EBUSY		16	/* Device or resource busy */#define	EEXIST		17	/* File exists */#define	EXDEV		18	/* Cross-device link */#define	ENODEV		19	/* No such device */#define	ENOTDIR		20	/* Not a directory */#define	EISDIR		21	/* Is a directory */#define	EINVAL		22	/* Invalid argument */#define	ENFILE		23	/* File table overflow */#define	EMFILE		24	/* Too many open files */#define	ENOTTY		25	/* Not a typewriter */#define	ETXTBSY		26	/* Text file busy */#define	EFBIG		27	/* File too large */#define	ENOSPC		28	/* No space left on device */#define	ESPIPE		29	/* Illegal seek */#define	EROFS		30	/* Read-only file system */#define	EMLINK		31	/* Too many links */#define	EPIPE		32	/* Broken pipe */#define	EDOM		33	/* Math argument out of domain of func */#define	ERANGE		34	/* Math result not representable */#endif

Chinese translation:

12345678910111213141516171819202122232425262728293031323334
#define EPERM 1 /* operation not permitted */#define ENOENT 2 /* No such file or directory */#define ESRCH 3 /* No such process */#define EINTR 4 /* Interrupted system call */#define EIO 5 /* I/O error */#define ENXIO 6 /* No such device or address */#define E2BIG 7 /* Argument list too long */#define ENOEXEC 8 /* Exec format error */#define EBADF 9 /* Bad file number */#define ECHILD 10 /* No child processes */#define EAGAIN 11 /* Try again */#define ENOMEM 12 /* Out of memory */#define EACCES 13 /* Permission denied */#define EFAULT 14 /* Bad address */#define ENOTBLK 15 /* Block device required */#define EBUSY 16 /* Device or resource busy */#define EEXIST 17 /* File exists */#define EXDEV 18 /* Cross-device link */#define ENODEV 19 /* No such device */#define ENOTDIR 20 /* Not a directory */#define EISDIR 21 /* Is a directory */#define EINVAL 22 /* Invalid argument */#define ENFILE 23 /* File table overflow */#define EMFILE 24 /* Too many open files */#define ENOTTY 25 /* Not a typewriter */#define ETXTBSY 26 /* Text file busy */#define EFBIG 27 /* File too large */#define ENOSPC 28 /* No space left on device */#define ESPIPE 29 /* Illegal seek */#define EROFS 30 /* Read-only file system */#define EMLINK 31 /* Too many links */#define EPIPE 32 /* Broken pipe */#define EDOM 33 /* Math argument outside domain of function */#define ERANGE 34 /* Math result not representable */

Example:

1234
if (IS_ERR(dev1.class)) {    ret = PTR_ERR(dev1.class);    goto fail_class_create;}

Errors sometimes cross kernel space and propagate to user space. If the returned error is a response to a system call (open, read, ioctl, mmap), the value is automatically assigned to the user-space errno global variable, on which callingstrerror(errno)can convert the error to a readable string:

123456
#include <errno.h> /* access errno global variable */#include <string.h>        if(wite(fd, buf, 1) < 0) {                printf("something gone wrong! %s\n", strerror(errno));        }

In kernel coding style:

  • If the function name is an action or an imperative command, the error code returned by the function should be an integer;
  • If the function name is a predicate, the function should return a boolean value succeeded (successful).

For example, add_work is a command, add_The work() function returns 0 for success and -EBUSY for failure.

Similarly, PCI device present is a predicate, pci_dev_The present() function returns 1 if it successfully finds a matching device; otherwise, it returns 0.

RK3568 Light up the LED

Schematic

The topeet RK3568 Working LED schematic is as follows

topeet RK3568 Working LED schematic
topeet RK3568 Working LED schematic

It can be seen that the LED is connected toGPIO0_B7this GPIO port. When GPIO0_When B7 is high, transistor Q16 conducts and LED9 lights up. When GPIO0_When B7 is low, transistor Q16 is cut off, and LED9 does not light up.

Query register address

Pin multiplexing of GPIO0B

First, we need to set the pin multiplexing of GPIO0B, which can be found in PMU_GRF.

GPIO0B multiplexing register address offset
GPIO0B multiplexing register address offset

Searching for GPIO0B7, we can see its corresponding register isPMU_GRF_GPIO0B_IOMUX_Hthe setting of bits 14:12

GPIOB7 pin multiplexing
GPIOB7 pin multiplexing

And the PMU_GRF register address can be seen below as 0xFDC20000

PMU_GRF
PMU_GRF

So the multiplexing register address = base address + offset address = 0xFDC20000 + 0xC = 0xFDC2000C.

Use the io command to view the address of this register:

1
io -r -4 0xFDC2000C

GPIO registers

From the Address Mapping in the TRM, we know that the GPIO0 address is 0xFDD60000

Address Mapping
Address Mapping

The GPIO chapter in the RK3568 TRM introduces:

3568 TRM
3568 TRM

It can be seen that the RK3568 registers are divided into:

  • Direction register (DDR)

    • GPIO_SWPORT_DR_L
    • GPIO_SWPORT_DR_H
  • Data register (DR)

    • GPIO_SWPORT_DDR_L
    • GPIO_SWPORT_DDR_H
  • External input register (EXT_PORT)

    • GPIO_EXT_PORT

that is,

  • GPIO_SWPORT_DDR_L/H Determines whether the pin is input or output
  • GPIO_SWPORT_DR_L/H Determines the output level when the pin is configured as output
  • GPIO_EXT_PORT Reflects the current actual level of the pin (read-only)

The data register and direction register are divided into L and H

  • GPIOA and GPIOB addresses fall on L
  • GPIOC and GPIOD addresses fall on H

Also:

five GPIOs's register group have five different base addresses
five GPIOs's register group have five different base addresses

It can be seen that the registers of the five groups GPIO0~GPIO4 are different.

We need to first set the GPIO direction register to output, then set the output level to high.

Register offset address
Register offset address

GPIO_EXT_PORT
GPIO_EXT_PORT

GPIO has four groups: GPIOA, GPIOB, GPIOC, GPIOD. Each group is further distinguished by numbering A0~A7, B0~B7, C0~C7, D0~D7.

GPIO0B7 is on L, so the offset address of the direction register is 0x0008, and the data register offset is 0x0000.

The base address of GPIO0 is 0xFDD60000. Therefore

  • Direction register address = base address + offset address = 0xFDD60000 + 0x0008 = 0xFDD60008

  • Data register address = base address + offset address = 0xFDD60000 + 0x0000 = 0xFDD60000

  • External input register address = base address + offset address = 0xFDD60000 + 0x0070 = 0xFDD60070

Direction register

GPIO_SWPORT_DDR_L
GPIO_SWPORT_DDR_L

It can be seen that bits 31:16 are writable, so GPIOB7 should be 16+8+8-1=31, that is,bit 31 sets write access

And 0+8+8-1=15, that is,bit 15 corresponds to setting the direction of GPIO0B7 as input or output

Data register

GPIO_SWPORT_DR_L
GPIO_SWPORT_DR_L

Similarly, 16+8+8-1=31, that is,bit 31 sets write access

And 0+8+8-1=15, that is,bit 15 corresponds to setting GPIO0B7 to output high or low level

Summary

  • Mux relationship registerThe base address is 0xFDC20000
    • GPIO0B_IOMUX_The H offset address is 000C, so the address to operate on is base address + offset address = 0xFDC2000C
      • GPIO0B_IOMUX_Bits 28 to 30 of H are bit write access, 0 is disable, 1 is enable
      • GPIO0B_IOMUX_Setting bits 12 to 14 of H to 0 selects GPIO0_B7, 1 is PWM0_M0, 2 is CPU_AVS
  • The base address of GPIO0 is 0xFDD60000
    • Direction registerAddress = base address + offset address = 0xFDD60008
      • bit 31 corresponds to the direction write access of GPIO0_B7, 0 is disable, 1 is enable
      • bit 15 corresponds to the direction of GPIO0_B7, 0 is input, 1 is output
    • Data registerAddress = base address + offset address = 0xFDD60000
      • bit 31 corresponds to the data write access of GPIO0_B7, 0 is disable, 1 is enable
      • bit 15 corresponds to the data of GPIO0_B7, 0 outputs low, 1 outputs high

Use the io command to test

12345678910111213141516171819202122
su root# Mux relationship registersudo io -4 -w 0xFDC2000C 0x70001000# Read to verify whether the write was successfulsudo io -4 -r 0xFDC2000C# Set outputsudo io -4 -w 0xFDD60008 0x80008000# Verifysudo io -4 -r 0xFDD60008# Read current valuesudo io -4 -r 0xFDD60000# Turn on the lightsudo io -4 -w 0xFDD60000 0x8000C000# Turn off the lightsudo io -4 -w 0xFDD60000 0x80004000

example

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
#include <linux/module.h>#include <linux/init.h>#include <linux/cdev.h>#include <linux/slab.h>#include <linux/io.h>#include <linux/uaccess.h>/* Multiplexing register */#define PMU_GRF_BASE 0xFDC20000#define GPIO0B_IOMUX_OFFSET 0xC#define GPIO0B_IOMUX PMU_GRF_BASE + GPIO0B_IOMUX_OFFSET/* Data register and direction register */#define GPIO0_BASE 0xFDD60000#define GPIO_SWPORT_DDR_L_OFFSET 0x8#define GPIO_SWPORT_DR_L_OFFSET 0x0#define GPIO_EXT_PORT_OFFSET 0x70// Direction register#define GPIO0_SWPORT_DDR_L GPIO0_BASE + GPIO_SWPORT_DDR_L_OFFSET// Data register#define GPIO0_SWPORT_DR_L GPIO0_BASE + GPIO_SWPORT_DR_L_OFFSET// External input register, read-only#define GPIO0_EXT_PORT GPIO0_BASE + GPIO_EXT_PORT_OFFSETstruct led_drv_data {        dev_t dev_num;        struct cdev cdev;        struct class *class;        struct device *dev;        void __iomem *gpio0b_iomux;        void __iomem *gpio0_swport_ddr_l;        void __iomem *gpio0_swport_dr_l;        void __iomem *gpio0_ext_port;};static struct led_drv_data *led_drv_data;int light_up_led_open(struct inode *inode, struct file *file){        file->private_data = led_drv_data;        return 0;}ssize_t light_up_led_read(struct file *file, char __user *buf, size_t size, loff_t *offset){        struct led_drv_data *drv_data = file->private_data;        u32 val;        if (size != sizeof(u32)) {                pr_info("read need 4 bytes\n");                return -ENOMEM;        }        val = readl(drv_data->gpio0_ext_port);        val &= 1 << 15;// gpio0_b7        val = val >> 15;        if (copy_to_user(buf, &val, sizeof(u32)) != 0) {                return -EFAULT;        }        return sizeof(u32);}ssize_t light_up_led_write(struct file *file, const char __user *buf, size_t size, loff_t *offset){        struct led_drv_data *drv_data = file->private_data;        u32 val;        if (size != sizeof(u32)) {                pr_err("write need 4 bytes\n");                return -ENOMEM;        }        if (copy_from_user(&val, buf, sizeof(u32)) != 0) {                return -EFAULT;        }        if (val > 0) {                // Configure as GPIO output                val = readl(drv_data->gpio0_swport_ddr_l);                val |= 0x80008000;                writel(val, drv_data->gpio0_swport_ddr_l);				// Open                val = readl(drv_data->gpio0_swport_dr_l);                val |= 0x80008000;                writel(val, drv_data->gpio0_swport_dr_l);        } else if (val == 0) {                // Configure as GPIO output                val = readl(drv_data->gpio0_swport_ddr_l);                val |= 0x80008000;                writel(val, drv_data->gpio0_swport_ddr_l);                // Shutdown                val = readl(drv_data->gpio0_swport_dr_l);                val |= 0x80000000;                val &= 0xffff7fff;                writel(val, drv_data->gpio0_swport_dr_l);        }        return sizeof(u32);}int light_up_led_release(struct inode *inode, struct file *file){        return 0;}struct file_operations fops = {        .owner = THIS_MODULE,        .open = light_up_led_open,        .read = light_up_led_read,        .write = light_up_led_write,        .release = light_up_led_release,};static int __init light_up_led_init(void){        int err;        u32 val;        led_drv_data = kzalloc(sizeof(struct led_drv_data), GFP_KERNEL);        if (led_drv_data == NULL) {                err = -ENOMEM;                goto kzalloc_fail;        }        err = alloc_chrdev_region(&led_drv_data->dev_num, 0, 1, "led chrdev region");        if (err < 0)                goto alloc_chrdev_region_fail;        cdev_init(&led_drv_data->cdev, &fops);        led_drv_data->cdev.owner = THIS_MODULE;        err = cdev_add(&led_drv_data->cdev, led_drv_data->dev_num, 1);        if (err < 0)                goto cdev_add_fail;        led_drv_data->class = class_create(THIS_MODULE, "test_led");        if (IS_ERR(led_drv_data->class)) {                err = PTR_ERR(led_drv_data->class);                goto create_class_fail;        }        led_drv_data->dev = device_create(led_drv_data->class, NULL, led_drv_data->dev_num, NULL,                                          "test_led%d", 0);        if (IS_ERR(led_drv_data->dev)) {                err = PTR_ERR(led_drv_data->dev);                goto device_create_fail;        }        led_drv_data->gpio0b_iomux = ioremap(GPIO0B_IOMUX, 4);        led_drv_data->gpio0_swport_ddr_l = ioremap(GPIO0_SWPORT_DDR_L, 4);        led_drv_data->gpio0_swport_dr_l = ioremap(GPIO0_SWPORT_DR_L, 4);        led_drv_data->gpio0_ext_port = ioremap(GPIO0_EXT_PORT, 4);        // Set pin multiplexing to GPIO        val = readl(led_drv_data->gpio0b_iomux);        val |= 0x70000000; // write access        val &= 0xFFFF8FFF; // gpio0_b7        writel(val, led_drv_data->gpio0b_iomux);        return 0;device_create_fail:        class_destroy(led_drv_data->class);create_class_fail:        cdev_del(&led_drv_data->cdev);cdev_add_fail:        unregister_chrdev_region(led_drv_data->dev_num, 1);alloc_chrdev_region_fail:        kfree(led_drv_data);kzalloc_fail:        return err;}static void __exit light_up_led_exit(void){        iounmap(led_drv_data->gpio0_ext_port);        iounmap(led_drv_data->gpio0b_iomux);        iounmap(led_drv_data->gpio0_swport_ddr_l);        iounmap(led_drv_data->gpio0_swport_dr_l);        device_destroy(led_drv_data->class, led_drv_data->dev_num);        class_destroy(led_drv_data->class);        cdev_del(&led_drv_data->cdev);        unregister_chrdev_region(led_drv_data->dev_num, 1);        kfree(led_drv_data);}module_init(light_up_led_init);module_exit(light_up_led_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("even629<asqwgo@163.com>");MODULE_DESCRIPTION("light up a led on topeet RK3568 board");

Test code:

123456789101112131415161718192021222324252627282930313233343536373839404142
#include <stdint.h>#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <string.h>#include <errno.h>int main(int argc, char **argv){        int fd, err;        uint32_t val = 0;        fd = open("/dev/test_led0", O_RDWR);        if (fd < 0)                goto fail;        if (argc == 1) { // read                err = read(fd, &val, 4);                if (err < 0)                        goto fail;                printf("read:0x%08x\n", val);        } else if (argc >= 2) { // write                val = atoi(argv[1]);                if (val < 0) {                        perror("unsupported\n");                        exit(EXIT_FAILURE);                }                err = write(fd, &val, 4);                if (err < 0)                        goto fail;                printf("write val: %u success\n", val);        } else {                perror("argc error\n");                exit(EXIT_FAILURE);        }        return 0;fail:        fprintf(stderr, "Error:%s[errno:%d]", strerror(errno), errno);        exit(EXIT_FAILURE);}
Loading comments…