Linux device driver
Driver: The software code that can control the hardware to implement specific functions is the driver.
What is the difference between ARM bare metal driver and driver?
The ARM bare metal driver is software code that is not based on the operating system. Usually this code is written independently by developers.
Drivers are software codes based on the kernel (Linux) architecture. They can not only operate the underlying hardware, but also need to interface with the Linux kernel. Usually Linux is code that has been written by kernel developers, and users only need to write drivers.
1. Linux kernel module
1. Three elements of kernel module:
Entry: Resource application work, executed during driver installation
Exit: Resource release work, executed when the write driver is uninstalled
License: The driver must comply with the GPL agreement (open source)
2. How to write the kernel module: (code block)
//内核的头文件在内核源码目录下通过vi -t xx查找
//ctags -R 创建索引
//或者在内核目录下 make tags
#include <linux/init.h>
#include <linux/module.h>
//1.入口
static int __init demo_init(void)
{
//static:修饰的函数只能在当前文件中使用
//int:这个函数的返回值类型
//__init:#define __init __section(".init.text")
//vmlinux.lds内核的链接脚本,.init.text这是脚本中的一个段
//内核源码--->vmlinux.lds--->uImage
//将驱动的入口函数都放在.init.text段中
//demo_init:入口函数的名字 led_init uart_init...
//(void) :入口函数没有参数
return 0;
}
//2.出口
static void __exit demo_exit(void)
{
//void类型没有返回值
//__exit告诉编译器将demo_exit函数放在.exit.text段中
}
module_init(demo_init);
//module_init是内核提供的宏
//告诉内核驱动的入口函数的地址
module_exit(demo_exit);
//告诉内核驱动的出口函数的地址
//3.许可证
MODULE_LICENSE("GPL"); //遵从开源协议
3. How to compile the kernel module:
The kernel module cannot be compiled directly using gcc when compiling. It should be because the driver code has source code that depends on the kernel.
Therefore, the compilation of the driver depends on the kernel, and a Makefile must be used to allow the driver to compile the driver.
1. Internal compilation: Compile in the kernel source tree
Kconfig .config Makefile
2. External compilation: Compile outside the kernel source tree (write your own general Makefile)
make arch=architecture modname=module name
arch ?=arm
modname ?=demo
ifeq ($(arch),arm)
#KERNELDIR:指向内核目录的一个变量
KERNELDIR:= /home/ubuntu/linux-5.10.61 #开发板上可安装的arm格式
else
KERNELDIR := /lib/modules/$(shell uname -r)/build/ #ubuntu可以安装的x86-64格式
endif
PWD :=$(shell pwd)
#当前路径$(shell pwd)在Makefile的时候起一个终端
#这个终端上执行pwd,将这个命令的结果赋值给PWD变量
all:
make -C $(KERNELDIR) M=$(PWD) modules
#make -C $(KERNELDIR)
#进入到内核顶层目录下,读取这个目录下的Makefile文件,然后执行make
# M=$(PWD) :指定编译模块的路径为当前路径
# make modules 模块化编译
#进入内核顶层目录下读取Makefile文件,然后进行模块化编译
#通过M指定编译的目录在当前目录
clean:
make -C $(KERNELDIR) M=$(PWD) clean
#清除编译
obj-m:=$(modname).o
#指定编译的当前目录的的模块名是$(modname)===>$(modname).ko
4. Installation and uninstallation of kernel modules
sudo insmod xxx.ko //Install kernel module
lsmod //View kernel modules
sudo rmmod xxx //Uninstall the driver module
5. Use of print statements in the kernel module printk
5.1 Usage format of printk:
printk(Print level "control format", parameter list); //Specify the level of the message
printk("Control format", parameter list); //Default level of message
Note: The usage of printk and printf in the kernel are the same except for the printing level. This printing level is used to filter the printing information.
5.2printk filter information method:
A message has a message level, and a terminal also has a terminal level. Only when the message level is greater than the terminal level, the message will be displayed on the terminal.
cat /proc/sys/kernel/printk
#define KERN_EMERG "0" /* system is unusable */ #define KERN_ALERT "1" /* action must be taken immediately */ #define KERN_CRIT "2" /* critical conditions */ #define KERN_ERR "3" /* error conditions */ #define KERN_WARNING "4" /* warning conditions */ #define KERN_NOTICE "5" /* normal but significant condition */ #define KERN_INFO "6" /* informational */ #define KERN_DEBUG "7" /* debug-level messages */
6. Modify the default level of messages
1. Modify the default printing level of ubuntu
su root
echo 4 3 1 7 > /proc/sys/kernel/printk
2. Modify the development board’s default printing level
rootfs/etc/init.d/rcS
Add --->echo 4 3 1 7 > /proc/sys/kernel/printk to the last line of this script
7. You can view the print information through commands (active view)
dmesg //View all the print information of the kernel from startup to the current moment
sudo dmesg -C or -c //Clear print information (-C clears directly, -c echoes first, then clears)
Note: If it is white, it means the message level is lower than the terminal level. If it is red, it means the message level is higher than the terminal level.
8. Kernel module parameter passing
sudo insmod demo.if a=10 b=20
The phenomenon seen through modinfo xxx.ko is: parm: ih:this is backlight var range[0-255] (int)
#include <linux/init.h>
#include <linux/module.h>
int a=20;
module_param(a,int,0664); // module_param(name, type, perm) 用来接收命令行传递过来的参数
//@name:变量名 @type:变量的类型 @perm:权限 (最大的权限是0664)
/*当这里的权限不是0的时候,它会在/sys/module/驱动名字目录/parameters/
目录下产生产生一个以name命名的文件,这个权限就是修饰这个文件权限的*/
//1.入口
static int __init demo_init(void)
{
printk(KERN_ERR "hello DC21121 everyone!!!\n");
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
printk("a = %d\n",a);
return 0;
}
//2.出口
static void __exit demo_exit(void)
{
printk(KERN_ERR "bye DC21121 everyone!!!\n");
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL"); //遵从开源协议
Notice:
1. Pass members of char type <=== Character types cannot be passed, only integers can be passed
2. Pass members of short type
3. Pass the string pointer (char *) member <==== There cannot be spaces in the string
sudo insmod 01module_param.ko ch=97 sh=200 ih=900 sp=hello everyone
#include <linux/init.h>
#include <linux/module.h>
char ch='A';
module_param(ch,byte,0664);
short sh=123;
module_param(sh,short,0664);
int ih=2222;
module_param(ih,int,0664);
char *sp = "hell world";
module_param(sp,charp,0664);
//1.入口
static int __init demo_init(void)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
printk("ch = %d\n",ch);
printk("sh = %d\n",sh);
printk("ih = %d\n",ih);
printk("sp = %s\n",sp);
return 0;
}
//2.出口
static void __exit demo_exit(void)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
printk("ch = %d\n",ch);
printk("sh = %d\n",sh);
printk("ih = %d\n",ih);
printk("sp = %s\n",sp);
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL"); //遵从开源协议
View the variables that can be passed as parameters in the module
modinfo 01module_param.ko
2. Character device driver
1.Character device driver architecture
2. Character device driver related APIs
Write character device driver:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#define CNAME "mycdev"
int major;
int mycdev_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t mycdev_read(struct file *file,
char __user *ubuf, size_t size, loff_t *offs)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t mycdev_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
//1.注册字符设备驱动
major = register_chrdev(0,CNAME,&fops);
if(major < 0){
printk("register char device driver error\n");
return major;
}
printk("register char device driver success... major = %d\n",major);
return 0;
}
static void __exit mycdev_exit(void)
{
//2.注销字符设备驱动
unregister_chrdev(major,CNAME);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
3. Character device driver testing process
1. Compile driver
Copy the source file of the driver from windows to ubuntu, and then use Makefile to compile it
make arch=x86 modname=mycdev
2.Install the driver
sudo insmod mycdev.ko
dmesg ====>Information printed in the entry function
cat /proc/devices ====>Major device number and device name
3. Create a device node for the driver
sudo mknod /dev/hello c 240 0
mknod: command to create nodes
/dev/hello: The path and name of the device node (any path is acceptable, it is customary to place it in /dev/)
c/b: c character device b block device
240: Major device number
0: Secondary device number (any one from 0 to 255 is acceptable)
Note: The device node is a file that the application accesses to the driver.
4. User space and kernel space data transfer
Data copy function API
5. Write LED driver: based on STM32MP157 microcontroller
address mapping
The operation of applying LED is completed by operating the register. The address of the register is the physical address, and the driver runs in 3-4G virtual memory. Therefore, if the LED register is operated in the kernel, the address of the LED register is mapped to the kernel space. .
The process of address mapping and unmapping can be completed through ioremap/iounmap.
myled.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include "myled.h"
#define CNAME "myled"
int major;
char kbuf[128] = {0};
unsigned int *virt_moder;
unsigned int *virt_odr;
unsigned int *virt_rcc;
int myled_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t myled_read(struct file *file,
char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
//如果用户想写的大小大于内核的内存大小,更正用户写的大小
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_to_user(ubuf,kbuf,size);
if(ret){ //成功返回0,失败返回未拷贝的字节的个数
printk("copy data to user error\n");
return -EIO; //失败返回错误码
}
return size; //成功返回拷贝的字节的个数
}
ssize_t myled_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
//如果用户想写的大小大于内核的内存大小,更正用户写的大小
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if(ret){ //成功返回0,失败返回未拷贝的字节的个数
printk("copy data from user error\n");
return -EIO; //失败返回错误码
}
printk("kernel data = %s\n",kbuf);
if(kbuf[0]=='1'){
*virt_odr |=(1<<10); //high
}else if(kbuf[0]=='0'){
*virt_odr &=~(1<<10); //low
}
return size; //成功返回拷贝的字节的个数
}
int myled_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fops = {
.open = myled_open,
.read = myled_read,
.write = myled_write,
.release = myled_close,
};
static int __init myled_init(void)
{
//1.注册字符设备驱动
major = register_chrdev(0,CNAME,&fops);
if(major < 0){
printk("register char device driver error\n");
return major;
}
printk("register myled driver success... major = %d\n",major);
//2.映射LED1的地址,并将LED1初始化为熄灭
virt_moder = ioremap(PHY_LED1_MODER,4);
if(virt_moder == NULL){
printk("ioremap moder addr error\n");
return -ENOMEM;
}
virt_odr = ioremap(PHY_LED1_ODR,4);
if(virt_odr == NULL){
printk("ioremap odr addr error\n");
return -ENOMEM;
}
virt_rcc = ioremap(PHY_LED1_RCC,4);
if(virt_rcc == NULL){
printk("ioremap rcc addr error\n");
return -ENOMEM;
}
*virt_rcc |= (1<<4); //rcc enable
*virt_moder &=~(3<<20);
*virt_moder |=(1<<20); //output
*virt_odr &=~(1<<10); //low
return 0;
}
static void __exit myled_exit(void)
{
iounmap(virt_rcc);
iounmap(virt_odr);
iounmap(virt_moder);
//2.注销字符设备驱动
unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
myled.h
#ifndef __MYLED_H__
#define __MYLED_H__
#define PHY_LED1_MODER 0x50006000
#define PHY_LED1_ODR 0x50006014
#define PHY_LED1_RCC 0x50000a28
#endif
test.c
#include <head.h> // arm-linux-gnueabihf-gcc test.c -I /usr/include
int main(int argc, const char *argv[])
{
int fd;
char buf[128] = {0};
if ((fd = open("/dev/myled", O_RDWR)) == -1)
{
perror("open error");
exit(EXIT_FAILURE);
}
while (1)
{
printf("input 0(off),1(on) > ");
fgets(buf, sizeof(buf), stdin);
buf[strlen(buf) - 1] = '\0';
write(fd, buf, sizeof(buf));
}
close(fd);
return 0;
}
3. Character device driver: udev mechanism
1. Mechanism for creating device nodes
- mknod: command to create device nodes
- devfs: is the mechanism for creating device nodes in early Linux. The logic of creating device nodes is in the kernel space (before version 2.4)
- udev: The logic of creating device nodes is in user space, from kernel version 2.6 to the present
- mdev: As a lightweight version of uudev mechanism, it is often used in some embedded chips.
2. The principle of creating device nodes by udev mechanism
1. The user submits the directory upward in the device driver, and the kernel will apply for a struct class type space, which stores the directory information. The kernel will create a directory under /sys/class/
2. We submit the node information upwards in the device driver, and apply for a struct device type space in the kernel, which stores the node information we submitted. The kernel will create a space to store the node information under /sys/class/directory/ document
3. After completing the above two steps, hotplug will notify udev to create a device file under /dev based on the information in /sys/class/directory/file that stores node information.
3. Create device node-related APIs
1.#include<linux/device.h>
struct class * class_create(struct module *owner, const char *name)
Function: Submit directory information upward (apply for a struct class variable space in the kernel and initialize it)
parameter:
owner : a pointer to a struct module type space, fill in THIS_MODULE (pointer to the current module)
name: Directory name Return value: Successfully returns the struct class space pointer with successful application
Failure returns error pointer
//4K space is reserved at the top level of the kernel. When the class_create function call fails, the return value is a pointer to the 4K reserved space bool __must_check IS_ERR(__force const void *ptr)
Function: Used to determine whether the pointer points to the 4K space reserved at the top level of the kernel
Return value: Returns true if the space is reserved, false if no space is reserved.
long __must_check PTR_ERR(__force const void *ptr)
Function: Get the absolute value of an error code based on the error code pointer
2.void class_destroy(struct class *cls);
Function: Destroy the applied struct class space
Parameters: cls: the first address of the space requested by cls_create
Return value: None
3. struct device *device_create (struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
Function: Submit device node information upward (apply for a struct device type space)
Parameters: class: first address of space applied for by class_create
parent: Specify the parent node pointer of the currently applied for struct device space, fill in NULL
devt: the device number of the device driver
MKDEV(major,minor)//Get the device number based on the major device number and minor device number
MAJOR(dev)//Get the main device number according to the device number
MINOR(dev)//Get the minor device number according to the device number
drvdata: a private data filled in the applied struct device space, just fill in NULL
fmt: the name of the device node
Return value: The first address of the applied struct device space is returned successfully, and the error pointer is returned if it fails.
4.void device_destroy(struct class *class, dev_t devt)
Function: Destroy the applied struct device space
Parameters: class:class_create the first address of the space applied for
devt: device number
Return value: None
How to determine the error code:
Note: Usage of multiple statements of macro
#include <head.h>
int max;
#define MAX(a, b) \
do \
{ \
if (a > b) \
max = a; \
else \
max = b; \
} while (0)
//do{}while(0) :可以有多条语句,但是乜有返回值,即使有return也不是宏的返回值
#define MAXX(a, b) ({int tt;if(a>b)tt=a;else tt=b; tt; })
//(())宏:可以有多条语句,最后一句话的结果就是宏的返回值
int main(int argc, const char *argv[])
{
MAX(100, 200);
printf("max = %d\n", max);
printf("max = %d\n",MAXX(1000,200));
return 0;
}
Automatically create instances of device nodes
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include "myled.h"
#define CNAME "myled"
#define LED1_ON (virt_led1->ODR |=(1<<10))
#define LED1_OFF (virt_led1->ODR &=~(1<<10))
#define LED2_ON (virt_led2->ODR |=(1<<10))
#define LED2_OFF (virt_led2->ODR &=~(1<<10))
#define LED3_ON (virt_led3->ODR |=(1<<8))
#define LED3_OFF (virt_led3->ODR &=~(1<<8))
int major;
char kbuf[2] = {0};
gpio_t *virt_led1;
gpio_t *virt_led2;
gpio_t *virt_led3;
unsigned int *virt_rcc;
struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t myled_read(struct file *file,
char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_to_user(ubuf,kbuf,size);
if(ret){
printk("copy data to user error\n");
return -EIO;
}
return size;
}
//kbuf[2] = {which,status};
//0 led1 1 led2 2 led3
//0 on 1 off
ssize_t myled_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if(ret){
printk("copy data from user error\n");
return -EIO;
}
switch(kbuf[0]){ //kbuf[0] which
case LED1:
//kbuf[1] status
kbuf[1]==1?LED1_ON:LED1_OFF;
break;
case LED2:
kbuf[1]==1?LED2_ON:LED2_OFF;
break;
case LED3:
kbuf[1]==1?LED3_ON:LED3_OFF;
break;
default:
printk("input arg error,try again\n");
return -EINVAL;
}
return size;
}
int myled_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fops = {
.open = myled_open,
.read = myled_read,
.write = myled_write,
.release = myled_close,
};
int all_led_init(void)
{
virt_led1 = ioremap(PHY_LED1_ADDR,sizeof(gpio_t));
if(virt_led1 == NULL){
printk("ioremap led1 addr error\n");
return -ENOMEM;
}
virt_led2 = ioremap(PHY_LED2_ADDR,sizeof(gpio_t));
if(virt_led2 == NULL){
printk("ioremap led2 addr error\n");
return -ENOMEM;
}
virt_led3 = virt_led1;
virt_rcc = ioremap(PHY_RCC_ADDR,4);
if(virt_rcc == NULL){
printk("ioremap rcc addr error\n");
return -ENOMEM;
}
*virt_rcc |= (3<<4); // rcc gpioe gpiof enable
//init led1
virt_led1->MODER &=~(3<<20);
virt_led1->MODER |=(1<<20); //output
virt_led1->ODR &=~(1<<10); //led1 off
//init led2
virt_led2->MODER &=~(3<<20);
virt_led2->MODER |=(1<<20); //output
virt_led2->ODR &=~(1<<10); //led2 off
//init led3
virt_led3->MODER &=~(3<<16);
virt_led3->MODER |=(1<<16); //output
virt_led3->ODR &=~(1<<8); //led3 off
return 0;
}
static int __init myled_init(void)
{
//1.注册字符设备驱动
major = register_chrdev(0,CNAME,&fops);
if(major < 0){
printk("register char device driver error\n");
return major;
}
printk("register myled driver success... major = %d\n",major);
//2.led地址映射及初始化
all_led_init();
//3.自动创建设备节点
cls = class_create(THIS_MODULE,"hello");
if(IS_ERR(cls)){
printk("class create error\n");
return PTR_ERR(cls);
}
dev = device_create(cls,NULL,MKDEV(major,0),NULL,"myled");
if(IS_ERR(dev)){
printk("device create error\n");
return PTR_ERR(dev);
}
return 0;
}
static void __exit myled_exit(void)
{
device_destroy(cls,MKDEV(major,0));
class_destroy(cls);
iounmap(virt_rcc);
iounmap(virt_led1);
iounmap(virt_led2);
unregister_chrdev(major,CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
4. Internal implementation of character device driver
1.Character device driver framework diagram
The file exists in the file system, and there will be an identification inode number. Based on this identification, the struct_inode structure (to save the current file information) is found. In the struct_inode structure, there is a character device pointer of type struct cdev *i_cdev, which points to the current drive object. struct cdev structure (character device driver object structure)/(the device number is the identification of the driver's kernel, and also the link between the device driver and the device file), and this structure stores the struct file_operations *ops operation method structure pointer; Then based on this struct file_operations *ops operation method structure pointer, the operation methods mycdev_open(), mycdev_read(), mycdev_write(), mycdev_close() are found; the operation method calls back to mycdev_open (user layer)
———————— ————————
2. Character device driver step-by-step implementation process (API)
#include <linux/cdev.h>
1. Character device driver structure
struct cdev { struct module *owner; //THIS_MODULE const struct file_operations *ops; //Operation method structure struct list_head list; //Constitute linked list dev_t dev; //Device number unsigned int count; / /number of devices };2. Allocate the character device driver object
struct cdev cdev;
struct cdev *cdev = cdev_alloc();
struct cdev *cdev_alloc(void)
function: allocate memory for the cdev structure pointer
Parameters:
@None
return value: successfully return the cdev structure Pointer, return NULL on failure
3. Character device driver object initialization
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
Function: Complete the initialization (part) of cdev structure members
Parameters:
@cdev: cdev structure pointer
@fops : Operation method structure pointer
Return value: None
int register_chrdev_region(dev_t from, unsigned count, const char *name)
Function: Statically specify the device number
Parameters:
@from: Specified device number (major device number | minor device number eg: 241 <<20|0)
@count: the number of devices (3)
@name: the name of the device driver cat /proc/devices view
Return value: 0 is returned on success, error code is returned on failure
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
Function: Dynamically apply for device number
Parameters:
@dev: applied for device number
@baseminor: secondary device Number starting value
@count: the number of devices (3)
@name: the name of the device driver cat /proc/devices Check
the return value: 0 is returned on success, error code 4 is returned on failure.
Register character device driver
int cdev_add(struct cdev *p , dev_t dev, unsigned count)
Function: Register character device driver
parameters:
@p:cdev structure pointer
@dev: device number
@count: number of devices
Return value: 0 is returned on success, error code is returned on failure
----- -------------------------------------------------- -------------------------------
1. Destroy the character device driver
void cdev_del(struct cdev *p)
Function: Destroy character device Driver
parameters:
@p: cdev structure pointer
Return value: none
2. Release device number
void unregister_chrdev_region(dev_t from, unsigned count)
Function: Release device number
Parameters:
@from: device number
@count: device number Number
Return value: None
3. Release the dynamically applied memory
void kfree(void *p)
Function: Release the dynamically applied memory
Parameters:
@p: the first address of the cdev structure
Return value: None
An example of implementing a character device driver step by step
basic framework
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
//定义cdev的结构体指针变量
struct cdev *cdev;
static int __init mycdev_init(void)
{
//1.分配对象
//2.对象的初始化
//3.申请设备号
//4.字符设备驱动的注册
//5.自动创建设备节点
return 0;
}
static void __exit mycdev_exit(void)
{
//1.销毁设备节点
//2.销毁字符设备驱动
//3.销毁设备号
//4.释放动态申请的cdev内存
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
mycdev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define CNAME "mycdev"
//定义cdev的结构体指针变量
struct cdev *cdev;
#if 0
unsigned int major = 0; //动态申请
#else
unsigned int major = 500; //静态指定
#endif
int minor=0;
const int count=3;
struct class *cls;
struct device *dev;
char kbuf[128] = {0};
int mycdev_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
ssize_t mycdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_to_user(ubuf,kbuf,size);
if(ret){
printk("copy data to user error\n");
return -EIO;
}
return size;
}
ssize_t mycdev_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
int ret;
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
if(size > sizeof(kbuf)) size=sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if(ret){
printk("copy data from user error\n");
return -EIO;
}
return size;
}
int mycdev_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
return 0;
}
const struct file_operations fops = {
.open = mycdev_open,
.read = mycdev_read,
.write = mycdev_write,
.release = mycdev_close,
};
static int __init mycdev_init(void)
{
int i,ret;
dev_t devno;
//1.分配对象
cdev = cdev_alloc();
if(cdev == NULL){
printk("cdev alloc memory error\n");
ret = -ENOMEM;
goto ERR1;
}
//2.对象的初始化
cdev_init(cdev,&fops);
//3.申请设备号
if(major == 0){
//动态申请
ret = alloc_chrdev_region(&devno,minor,count,CNAME);
if(ret){
printk("dynamic:alloc device number error\n");
goto ERR2;
}
major = MAJOR(devno);
minor = MINOR(devno);
}else if(major > 0){
//静态指定
ret = register_chrdev_region(MKDEV(major,minor),count,CNAME);
if(ret){
printk("static:alloc device number error\n");
goto ERR2;
}
}
//4.字符设备驱动的注册
ret = cdev_add(cdev,MKDEV(major,minor),count);
if(ret){
printk("cdev register error\n");
goto ERR3;
}
//5.自动创建设备节点
cls = class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls)){
printk("class create error\n");
ret = PTR_ERR(cls);
goto ERR4;
}
for(i=0;i<count;i++){
dev = device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);
if(IS_ERR(dev)){
printk("device create error\n");
ret = PTR_ERR(dev);
goto ERR5;
}
}
return 0; //!!!!!!!这里的return 0千万不要忘记写!!!!!!!!!!!!!!
ERR5:
for(--i;i>=0;i--){
device_destroy(cls,MKDEV(major,i));
}
class_destroy(cls);
ERR4:
cdev_del(cdev);
ERR3:
unregister_chrdev_region(MKDEV(major,minor),count);
ERR2:
kfree(cdev);
ERR1:
return ret;
}
static void __exit mycdev_exit(void)
{
int i;
//1.销毁设备节点
for(i=0;i<count;i++){
device_destroy(cls,MKDEV(major,i));
}
class_destroy(cls);
//2.销毁字符设备驱动
cdev_del(cdev);
//3.销毁设备号
unregister_chrdev_region(MKDEV(major,minor),count);
//4.释放动态申请的cdev内存
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
Five, the use of ioctl function in Linux system
1. ioctl function API
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long request, ...);
Function: control device
Parameters:
@fd; File descriptor
@request: Command code
@...: Writable, optional Write, if you fill in the address,
return value: 0 is returned if successful, -1 is returned if failed. Set error code user
———————————————————————————— ————————————————
struct file_operations kernel
long (*unlocked_ioctl) (struct file *, unsigned int cmd, unsigned long arg)
{ //The application layer request is passed to cmd //Application layer's...passed to arg }
2. Command code of ioctl function
====== ==================================
bits meaning
====== ==================================
31-30 00 - no parameters: uses _IO macro
10 - read: _IOR
01 - write: _IOW
11 - read/write: _IOWR
29-16 size of arguments
15-8 ascii character supposedly
unique to each driver
7-0 function #
====== ==================================
If you want to get the above 32-bit naming code that expresses a certain function, you need to implement it through the following macro
3. Example of ioctl
myled.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/device.h>
#include "myled.h"
#define CNAME "myled"
#define LED1_ON (virt_led1->ODR |= (1 << 10))
#define LED1_OFF (virt_led1->ODR &= ~(1 << 10))
#define LED2_ON (virt_led2->ODR |= (1 << 10))
#define LED2_OFF (virt_led2->ODR &= ~(1 << 10))
#define LED3_ON (virt_led3->ODR |= (1 << 8))
#define LED3_OFF (virt_led3->ODR &= ~(1 << 8))
int major;
char kbuf[2] = {0};
gpio_t *virt_led1;
gpio_t *virt_led2;
gpio_t *virt_led3;
unsigned int *virt_rcc;
struct class *cls;
struct device *dev;
int myled_open(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
ssize_t myled_read(struct file *file,
char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_to_user(ubuf, kbuf, size);
if (ret)
{
printk("copy data to user error\n");
return -EIO;
}
return size;
}
// kbuf[2] = {which,status};
// 0 led1 1 led2 2 led3
// 0 on 1 off
ssize_t myled_write(struct file *file,
const char __user *ubuf, size_t size, loff_t *off)
{
int ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
if (size > sizeof(kbuf))
size = sizeof(kbuf);
ret = copy_from_user(kbuf, ubuf, size);
if (ret)
{
printk("copy data from user error\n");
return -EIO;
}
return size;
}
long myled_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
//int which, ret;
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
switch(cmd){
case LED1ON:
LED1_ON;
break;
case LED1OFF:
LED1_OFF;
break;
}
// switch (cmd)
// {
// case LED_ON:
// ret = copy_from_user(&which, (void *)arg, sizeof(int));
// if (ret)
// {
// printk("copy data from user error\n");
// return -EINVAL;
// }
// switch (which)
// {
// case LED1:
// LED1_ON;
// break;
// case LED2:
// LED2_ON;
// break;
// case LED3:
// LED3_ON;
// break;
// }
// break;
// case LED_OFF:
// ret = copy_from_user(&which, (void *)arg, sizeof(int));
// if (ret)
// {
// printk("copy data from user error\n");
// return -EINVAL;
// }
// switch (which)
// {
// case LED1:
// LED1_OFF;
// break;
// case LED2:
// LED2_OFF;
// break;
// case LED3:
// LED3_OFF;
// break;
// }
// break;
// break;
// }
return 0;
}
int myled_close(struct inode *inode, struct file *file)
{
printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
return 0;
}
const struct file_operations fops = {
.open = myled_open,
.read = myled_read,
.write = myled_write,
.unlocked_ioctl = myled_ioctl,
.release = myled_close,
};
int all_led_init(void)
{
virt_led1 = ioremap(PHY_LED1_ADDR, sizeof(gpio_t));
if (virt_led1 == NULL)
{
printk("ioremap led1 addr error\n");
return -ENOMEM;
}
virt_led2 = ioremap(PHY_LED2_ADDR, sizeof(gpio_t));
if (virt_led2 == NULL)
{
printk("ioremap led2 addr error\n");
return -ENOMEM;
}
virt_led3 = virt_led1;
virt_rcc = ioremap(PHY_RCC_ADDR, 4);
if (virt_rcc == NULL)
{
printk("ioremap rcc addr error\n");
return -ENOMEM;
}
*virt_rcc |= (3 << 4); // rcc gpioe gpiof enable
// init led1
virt_led1->MODER &= ~(3 << 20);
virt_led1->MODER |= (1 << 20); // output
virt_led1->ODR &= ~(1 << 10); // led1 off
// init led2
virt_led2->MODER &= ~(3 << 20);
virt_led2->MODER |= (1 << 20); // output
virt_led2->ODR &= ~(1 << 10); // led2 off
// init led3
virt_led3->MODER &= ~(3 << 16);
virt_led3->MODER |= (1 << 16); // output
virt_led3->ODR &= ~(1 << 8); // led3 off
return 0;
}
static int __init myled_init(void)
{
// 1.注册字符设备驱动
major = register_chrdev(0, CNAME, &fops);
if (major < 0)
{
printk("register char device driver error\n");
return major;
}
printk("register myled driver success... major = %d\n", major);
// 2.led地址映射及初始化
all_led_init();
// 3.自动创建设备节点
cls = class_create(THIS_MODULE, "hello");
if (IS_ERR(cls))
{
printk("class create error\n");
return PTR_ERR(cls);
}
dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "myled");
if (IS_ERR(dev))
{
printk("device create error\n");
return PTR_ERR(dev);
}
return 0;
}
static void __exit myled_exit(void)
{
device_destroy(cls, MKDEV(major, 0));
class_destroy(cls);
iounmap(virt_rcc);
iounmap(virt_led1);
iounmap(virt_led2);
unregister_chrdev(major, CNAME);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
myled.h
#ifndef __MYLED_H__
#define __MYLED_H__
typedef struct{
volatile unsigned int MODER;
volatile unsigned int OTYPER;
volatile unsigned int OSPEEDR;
volatile unsigned int PUPDR;
volatile unsigned int IDR;
volatile unsigned int ODR;
volatile unsigned int BSRR;
}gpio_t;
#define PHY_RCC_ADDR 0x50000a28
#define PHY_LED1_ADDR 0x50006000
#define PHY_LED2_ADDR 0x50007000
#define PHY_LED3_ADDR 0x50006000
enum{
LED1,
LED2,
LED3
};
#define LED_ON _IOW('a',0,int)
#define LED_OFF _IOW('a',1,int)
#define LED1ON _IO('a',3)
#define LED1OFF _IO('a',4)
#endif
test.c
#include <head.h>
#include "myled.h"
int main(int argc, const char *argv[])
{
int fd;
int which;
if ((fd = open("/dev/myled", O_RDWR)) == -1)
{
perror("open error");
exit(EXIT_FAILURE);
}
while (1)
{
// which = LED1;
// ioctl(fd, LED_ON, &which);
// sleep(1);
// ioctl(fd, LED_OFF, &which);
// sleep(1);
ioctl(fd, LED1ON);
sleep(1);
ioctl(fd, LED1OFF);
sleep(1);
}
close(fd);
return 0;
}