a brief introduction
A Linux device driver refers to a software module that drives the Linux kernel to communicate with a hardware device. Device drivers are usually divided into two categories: character device drivers and block device drivers.
The main functions of the device driver include:
- Device initialization: When the system starts, the device driver needs to initialize the corresponding hardware device, set the parameters of the device's registers and interfaces, so as to ensure that the device can work normally.
- Device control: The device driver needs to provide some interfaces to control various operations of the device, such as opening the device, reading data, writing data, and closing the device.
- Interrupt handling: The device driver needs to handle the interrupt request of the hardware device, and execute the corresponding interrupt processing program when the interrupt occurs, so as to respond to various events and requests of the device in time.
- Data transmission: The device driver needs to implement the data transmission function, including operations such as reading data from the hardware device and writing data to the hardware device.
- Error handling: The device driver needs to handle errors and exceptions that occur in the device, such as device read and write errors, interrupt loss, etc.
1.1 Device driver classification
1.1.1 Block Device Driver
A block device driver refers to a driver program that communicates with a device in units of blocks, such as storage device drivers such as hard drives, solid state drives, and USB flash drives.
Block device drivers usually adopt a hierarchical structure, including the following levels:
- Device Driver Layer: This layer communicates directly with the hardware device and controls various operations of the device.
- Storage volume manager layer: This layer is responsible for managing storage volumes (such as hard disk partitions, logical volumes, etc.) and providing access interfaces to volumes.
- File system layer: This layer provides a file system interface to the volume, enabling users to access and manage files according to the directory structure of the file system.
1.1.2 Character device driver
I2C/SPI devices can be character devices, block devices, or network devices, depending on their functionality and application scenarios.
However, for some I2C devices, they may be classified as other types of devices such as block devices or network devices. For example, certain I2C memories such as EEPROMs can be considered examples of block devices because they transfer data in blocks. Other I2C devices, such as temperature sensors or light sensors, can be considered network devices because they transmit data in packets.
Therefore, not all I2C/SPI devices are character devices , but may be classified into different types of devices according to their functions and application scenarios. Different types of devices require different drivers to access and control them.
The call of the driver program by the Linux application program is shown in Figure 40.1.1
Applications run in user space, while Linux drivers are part of the kernel, so drivers run in kernel space.When we want to implement operations on the kernel in user space, such as using the open function to open the /dev/led driver, because usingUser space cannot directly operate on the kernel, so a method called "system call" must be used to implementTime "trapped" into the kernel space, so as to realize the operation of the underlying driver.
Two device driver principles
2.1 Loading and unloading of the driver module
2.1.1 Loading
module_init(xxx_init); //register module loading functionmodule_exit(xxx_exit); //register module uninstall function
After the driver is compiled, the extension is .ko . There are two commands to load the driver module: insmod and modprobe
insmod drv.komodprobe drv.ko
2.1.2 Uninstall
rmmod drv.komodprobe -r drv.ko //Use the modprobe command to uninstall other modules that the driver module depends on, provided that these dependent modules are no longerIt is used by other modules, otherwise you cannot use modprobe to unload the driver module. So for module uninstallation, it is recommended to use the rmmod command
2.2 Address Mapping MMU
When the Linux kernel starts, it will initialize the MMU , set up the memory map, and after setting up, all CPU accesses are virtualproposed address.
2.2.1 ioremap function
The ioremap function is used to obtain the virtual address space corresponding to the specified physical address space. It is defined in the arch/arm/include/asm/io.h file and is defined as follows:
示例代码 41.1.1.1 ioremap 函数
#define ioremap(cookie,size) __arm_ioremap((cookie), (size),
MT_DEVICE)
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size,
unsigned int mtype)
{
return arch_ioremap_caller(phys_addr, size, mtype,
__builtin_return_address(0));
}
2.2.2 iounmap function
void iounmap (volatile void __iomem *addr)
2.2.3 I/O memory access functions
Linux indirectly manipulates physical addresses by manipulating virtual addresses
u8 readb(const volatile void __iomem *addr)
u16 readw(const volatile void __iomem *addr)
u32 readl(const volatile void __iomem *addr)
2 write operation function
void writeb(u8 value, volatile void __iomem *addr)
void writew(u16 value, volatile void __iomem *addr)
void writel(u32 value, volatile void __iomem *addr)
2.3 Device number
2.3.1 Composition
The upper 12 bits of the dev_t __u32 type are the major device number, and the lower 20 bits are the minor device number. Therefore LinuxThe main equipment number in the system ranges from 0 to 4095 , so you must not exceed this range when selecting the main equipment number.
2.3.2 Device number allocation and cancellation
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)
2.4 New character device registration method
2.4.1 , character device structure
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
2.4.2 , cdev_init function
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
/* newchrled设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/* 2、初始化cdev */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
2.4.3, cdev_add function
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
2.4.4, cdev_del function
void cdev_del(struct cdev *p)
2.5 Automatically create device nodes
2.5.1 mdev mechanism
echo /sbin/mdev > /proc/sys/kernel/hotplug
2.5.1.1 Creating and deleting classes
struct class *class_create (struct module *owner, const char *name)
void class_destroy(struct class *cls);
2.5.1.2 Create Device
struct device *device_create(struct class*class,struct device *parent,dev_tdevt,void*drvdata,const char*fmt, ...)
void device_destroy(struct class *class, dev_t devt)
2.6 Set file private data
/* newchrled设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled; /* 设置私有数据 */
return 0;
}
2.7 Generic device driver creation template reference example
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
/* newchrled设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct newchrled_dev newchrled; /* led设备 */
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做private_data的成员变量
* 一般在open的时候将private_data指向设备结构体。
* @return : 0 成功;其他 失败
*/
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled; /* 设置私有数据 */
return 0;
}
/*
* @description : 从设备读取数据
* @param - filp : 要打开的设备文件(文件描述符)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @description : 向设备写数据
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 要写给设备写入的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* 设备操作函数 */
static struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static int __init led_init(void)
{
...
/* 注册字符设备驱动 */
/* 1、创建设备号 */
if (newchrled.major) { /* 定义了设备号 */
newchrled.devid = MKDEV(newchrled.major, 0);
register_chrdev_region(newchrled.devid, NEWCHRLED_CNT, NEWCHRLED_NAME);
} else { /* 没有定义设备号 */
alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_CNT, NEWCHRLED_NAME); /* 申请设备号 */
newchrled.major = MAJOR(newchrled.devid); /* 获取分配号的主设备号 */
newchrled.minor = MINOR(newchrled.devid); /* 获取分配号的次设备号 */
}
printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);
/* 2、初始化cdev */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
/* 3、添加一个cdev */
cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_CNT);
/* 4、创建类 */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if (IS_ERR(newchrled.class)) {
return PTR_ERR(newchrled.class);
}
/* 5、创建设备 */
newchrled.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
if (IS_ERR(newchrled.device)) {
return PTR_ERR(newchrled.device);
}
return 0;
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit led_exit(void)
{
....
/* 注销字符设备驱动 */
cdev_del(&newchrled.cdev);/* 删除cdev */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */
device_destroy(newchrled.class, newchrled.devid);
class_destroy(newchrled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
// MODULE_VERSION("4.1.15-g3dc0a4b SMP preempt mod_unload modversions ARMv7 p2v8")