字符设备驱动基础

1 字符设备驱动框架

作为字符设备驱动的三要素:

    crw-r----- 1 root root 13, 64 Mar 28 20:14 event0
    crw-r----- 1 root root 13, 65 Mar 28 20:14 event1
    crw-r----- 1 root root 13, 66 Mar 28 20:14 event2

2 作为驱动必须有一个主设备号--向系统申请

申请设备号
int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops)
参数1:主设备号
        设备号(32bit--dev_t)==主设备号(12bit) + 次设备号(20bit)
            主设备号:表示一类设备--camera
            次设备号: 表示一类设备中某一个:前置,后置
        给定到方式有两种:
        1,动态--参数1直接填0
        2,静态--指定一个整数,250

参数2: 描述一个设备信息,可以自定义
		/proc/devices列举出所有到已经注册的设备

参数3: 文件操作对象--提供open, read,write
返回值: 正确返回0,错误返回负数
注销设备号
void unregister_chrdev(unsigned int major, const char * name)
参数1:主设备号
参数2: 描述一个设备信息,可以自定义

示例代码

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

unsigned int major_id = 502;
const char major_name[] = "test";
const struct file_operations major_fops = {
	};

static int __init my_chrdrv_init(void)
{
	//一般都是申请资源
	//申请设备号
	int ret = register_chrdev(major_id, major_name, &major_fops);
	if (ret == 0){
		printk("register ok\n");
		}
	else{
		printk("register failed");
		return -EFAULT;
	}
	return 0;
}

static void __init my_chrdrv_exit(void)
{
	//一般都是释放资源
	//释放设备号资源
	unregister_chrdev(major_id, major_name);	
}

MODULE_LICENSE("GPL");

module_init(my_chrdrv_init);
module_exit(my_chrdrv_exit);

效果展示:

[root@farsight ]# cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
...
253 tpm
254 gpiochip
[root@farsight ]# insmod chr_drv.ko
[  117.470884] chr_drv: loading out-of-tree module taints kernel.
[  117.476144] register ok
[root@farsight ]# cat /proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
...
253 tpm
254 gpiochip
502 test
[root@farsight ]# rmmod chr_drv.ko
[root@farsight ]# cat proc/devices
Character devices:
  1 mem
  2 pty
  3 ttyp
  4 /dev/vc/0
  4 tty
  4 ttyS
···
252 rtc
253 tpm
254 gpiochip

3.创建设备节点:

1,手动创建--缺点/dev/目录中文件都是在内存中,断电后/dev/文件就会消失

    mknod /dev/设备名  类型  主设备号 次设备号
    比如:
        mknod  /dev/mychr0  c  502 0


    [root@farsight ]# mknod /dev/mychr0 c 502 0
    [root@farsight ]# ls /dev/mychr0 -l
    crw-r--r--    1 0        0         502,   0 Jan  1 00:01 /dev/mychr0

2,自动创建(通过udev/mdev机制)

    struct class *class_create(owner, name)//创建一个类
        参数1: THIS_MODULE
        参数2: 字符串名字,自定义
        返回一个class指针

    //创建一个设备文件
    struct device *device_create(struct class * class, struct device * parent, dev_t devt, 
                        void * drvdata, const char * fmt,...)
    参数1: class结构体,class_create调用之后到返回值
    参数2:表示父亲,一般直接填NULL
    参数3: 设备号类型 dev_t
            dev_t devt
                #define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
                #define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
                #define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))
    参数4:私有数据,一般直接填NULL
    参数5和6:表示可变参数,字符串,表示设备节点名字
销毁动作:
        void device_destroy(devcls,  MKDEV(dev_major, 0));
        参数1: class结构体,class_create调用之后到返回值
        参数2: 设备号类型 dev_t

        void class_destroy(devcls);
        参数1: class结构体,class_create调用之后到返回值

效果展示

    [root@farsight ]# insmod chr_drv.ko [   33.757337] VMEM_VDD_2.8V: disabling
    [   34.262594] chr_drv: loading out-of-tree module taints kernel.
    [   34.268058] register ok
    [root@farsight ]# rmmod chr_drv.ko
    [root@farsight ]#

4 在驱动中实现文件io的接口,应用程序可以调用文件io

    struct file_operations
		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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
		ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
		int (*iterate) (struct file *, struct dir_context *);
		unsigned int (*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 *);
		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 (*aio_fsync) (struct kiocb *, 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 **);
		long (*fallocate)(struct file *file, int mode, loff_t offset,
				  loff_t len);
		int (*show_fdinfo)(struct seq_file *m, struct file *f);
		}; //函数指针的集合,其实就是接口,我们写驱动到时候需要去实现

		const struct file_operations my_fops = {
				.open = chr_drv_open,
				.read = chr_drv_read,
				.write = chr_drv_write,
				.release = chr_drv_close,
		};
			fd = open("/dev/chr2", O_RDWR);
			if(fd < 0)
			{
				perror("open");
				exit(1);
			}

			read(fd, &value, 4);

			write(fd, &value, 4);


			close(fd);

代码示例

//------------------chr_drv.c------------------
//驱动程序代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>

unsigned int major_id = 502;
const char major_name[] = "test";
struct class* pmyclass = NULL;
struct device* pmydevice =NULL;

int chr_drv_open (struct inode *inode, struct file *filp)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}
ssize_t chr_drv_read (struct file * filp, char __user *buf, size_t count, loff_t *fpos)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}

ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}

int chr_drv_close (struct inode *inode, struct file *filp)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}

const struct file_operations major_fops = {
		.open = chr_drv_open,
		.read = chr_drv_read,
		.write = chr_drv_write,
		.release = chr_drv_close,
	};

static int __init my_chrdrv_init(void)
{
	//一般都是申请资源
	//申请设备号
	int ret = register_chrdev(major_id, major_name, &major_fops);
	if (ret == 0){
		printk("register ok\n");
		}
	else{
		printk("register failed");
		return -EFAULT;
	}

	pmyclass = class_create(THIS_MODULE,"TEST1");
	pmydevice = device_create(pmyclass,NULL,MKDEV(502, 0),NULL,"mychr0");

	
	return 0;
}

static void __exit my_chrdrv_exit(void)
{
	//一般都是释放资源
	//释放设备号资源
	unregister_chrdev(major_id, major_name);	
	device_destroy(pmyclass,MKDEV(502, 0));
	class_destroy(pmyclass);
}

MODULE_LICENSE("GPL");

module_init(my_chrdrv_init);
module_exit(my_chrdrv_exit);

//----------------------drvtest.c-----------------------
//APP 代码 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int fd;
	int buf = 0;
	
	fd = open("/dev/mychr0",O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}
	
	write(fd, &buf, 4);
	
	read(fd, &buf, 4);
	
	close(fd);

	return 0;
}
# ----------------Makefile----------------------
ROOTFS_DIR = /home/linux/Level11

APP_NAME = drvtest

CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE), )

KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79
CUR_DIR = $(shell pwd)

all :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
		$(CC) $(APP_NAME).c -o $(APP_NAME)

clean :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) clean

install:
		cp -raf  *.ko  $(APP_NAME)   $(ROOTFS_DIR)/drv_module
		
else
obj-m += chr_drv.o

endif

效果展示

[root@farsight ]# insmod chr_drv.ko
[ 5820.214768] register ok
[root@farsight ]# ./drvtest
[ 5828.839807] ------------chr_drv_open----------
[ 5828.842812] ------------chr_drv_write----------
[ 5828.847978] ------------chr_drv_read----------
[ 5828.851741] ------------chr_drv_close----------
[root@farsight ]#

5 应用程序需要传递数据给驱动


int copy_to_user(void __user * to, const void * from, unsigned long n)
//将数据从内核空间拷贝到用户空间,一般是在驱动中chr_drv_read()用
参数1:应用驱动中的一个buffer
参数2:内核空间到一个buffer
参数3:个数
返回值:大于0,表示出错,剩下多少个没有拷贝成功
等于0,表示正确

int copy_from_user(void * to, const void __user * from, unsigned long n)
//将数据从用户空间拷贝到内核空间,一般是在驱动中chr_drv_write()用
参数1:内核驱动中的一个buffer
参数2:应用空间到一个buffer
参数3:个数

代码展示:

//------------------chr_drv.c------------------
//驱动程序代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
 
unsigned int major_id = 502;
const char major_name[] = "test";
struct class* pmyclass = NULL;
struct device* pmydevice =NULL;


int chr_drv_open (struct inode *inode, struct file *filp)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}
ssize_t chr_drv_read (struct file * filp, char __user *buf, size_t count, loff_t *fpos)
{
	int buf_read = 666;
	int ret;
	
	ret = copy_to_user(buf,&buf_read,count);
	if(ret == 0)
	{
		printk("------------%s----------\n",__FUNCTION__);
	}
	else 
	{
		printk("KERN:Error to read\n");
		return -EFAULT;
	}
	return 0;
}

ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	int buf_write = 0;
	int ret; 
	
	ret = copy_from_user(&buf_write,buf,count);
	
	if(ret == 0)
	{
		printk("------------KERN:%d----------\n",buf_write);
	}
	else 
	{
		printk("KERN:Error to read\n");
		return -EFAULT;
	}
	return 0;
}

int chr_drv_close (struct inode *inode, struct file *filp)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}

const struct file_operations major_fops = {
		.open = chr_drv_open,
		.read = chr_drv_read,
		.write = chr_drv_write,
		.release = chr_drv_close,
	};

static int __init my_chrdrv_init(void)
{
	//一般都是申请资源
	//申请设备号
	int ret = register_chrdev(major_id, major_name, &major_fops);
	if (ret == 0){
		printk("register ok\n");
		}
	else{
		printk("register failed");
		return -EFAULT;
	}

	pmyclass = class_create(THIS_MODULE,"TEST1");
	pmydevice = device_create(pmyclass,NULL,MKDEV(502, 0),NULL,"mychr0");

	
	return 0;
}

static void __exit my_chrdrv_exit(void)
{
	//一般都是释放资源
	//释放设备号资源
	unregister_chrdev(major_id, major_name);	
	device_destroy(pmyclass,MKDEV(502, 0));
	class_destroy(pmyclass);
}

MODULE_LICENSE("GPL");

module_init(my_chrdrv_init);
module_exit(my_chrdrv_exit);
//--------------drvtest.c-------------------
//APP 代码 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int fd;
	int buf = 233;
	
	fd = open("/dev/mychr0",O_RDWR);
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}
	write(fd, &buf, 4);
	read(fd, &buf, 4);
	printf("----------APP:%d---------\n",buf);
	
	
	close(fd);

	return 0;
}

# ----------------Makefile----------------------
ROOTFS_DIR = /home/linux/Level11

APP_NAME = drvtest

CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE), )

KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79
CUR_DIR = $(shell pwd)

all :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
		$(CC) $(APP_NAME).c -o $(APP_NAME)

clean :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) clean

install:
		cp -raf  *.ko  $(APP_NAME)   $(ROOTFS_DIR)/drv_module
		
else
obj-m += chr_drv.o

endif

效果展示:

[root@farsight ]# ./drvtest
[10946.384325] ------------chr_drv_open----------
[10946.387390] ------------KERN:233----------
[10946.391385] ------------chr_drv_read----------
[10946.396507] ------------chr_drv_close----------
----------APP:666---------
[root@farsight ]#

6. 控制外设,其实就是控制地址,内核驱动中是通过虚拟地址操作

    void *ioremap(cookie, size)
        参数1: 物理地址
        参数2: 长度
        返回值: 虚拟地址

    去映射--解除映射
        void iounmap(void __iomem *addr)
        参数1: 映射之后到虚拟地址

7. 通过驱动控制led灯:

    led---  GPX2_7 ---  GPX2CON ==0x11000C40
                        GPX2DAT ==0x11000C440x11000C40映射成虚拟地址
    对虚拟地址中到[32:28] = 0x1

代码展示:

//------------------chr_drv.c------------------
//驱动程序代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>

#define GPX2CON_ADDR 0x11000c40
#define GPX2CON_SIZE 8
 
unsigned int major_id = 502;
const char major_name[] = "test";
struct class* pmyclass = NULL;
struct device* pmydevice =NULL;
volatile unsigned int* GPX2CON;
volatile unsigned int* GPX2DAT;

int chr_drv_open (struct inode *inode, struct file *filp)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}
ssize_t chr_drv_read (struct file * filp, char __user *buf, size_t count, loff_t *fpos)
{
	int buf_read = 666;
	int ret;
	
	ret = copy_to_user(buf,&buf_read,count);
	if(ret == 0)
	{
		printk("------------%s----------\n",__FUNCTION__);
	}
	else 
	{
		printk("KERN:Error to read\n");
		return -EFAULT;
	}
	return 0;
}

ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	int buf_write = 0;
	int ret; 
	
	ret = copy_from_user(&buf_write,buf,count);

	if(ret == 0)
	{
		printk("__KERN__:%d\n",buf_write);
		if(buf_write == 0){
			*GPX2DAT &= ~(0x1<<7);								
		}
		else{
			*GPX2DAT |= (0x1<<7);
		}
	}
	else 
	{
		printk("KERN:Error to read\n");
		return -EFAULT;
	}
	return 0;
}

int chr_drv_close (struct inode *inode, struct file *filp)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}

const struct file_operations major_fops = {
		.open = chr_drv_open,
		.read = chr_drv_read,
		.write = chr_drv_write,
		.release = chr_drv_close,
	};

static int __init my_chrdrv_init(void)
{
	//一般都是申请资源
	//申请设备号
	int ret = register_chrdev(major_id, major_name, &major_fops);
	if (ret == 0){
		printk("register ok\n");
		}
	else{
		printk("register failed");
		return -EFAULT;
	}

	pmyclass = class_create(THIS_MODULE,"TEST1");
	pmydevice = device_create(pmyclass,NULL,MKDEV(502, 0),NULL,"mychr0");

	GPX2CON = (unsigned int*)ioremap(GPX2CON_ADDR,GPX2CON_SIZE) ;
	GPX2DAT = GPX2CON + 1;
	*GPX2CON &= ~(0xf << 28);
	*GPX2CON |= (0x1 << 28);
	
	return 0;
}

static void __exit my_chrdrv_exit(void)
{
	//一般都是释放资源
	//释放设备号资源
	unregister_chrdev(major_id, major_name);	
	device_destroy(pmyclass,MKDEV(502, 0));
	class_destroy(pmyclass);
	iounmap(GPX2CON);
}

MODULE_LICENSE("GPL");

module_init(my_chrdrv_init);
module_exit(my_chrdrv_exit);
//--------------drvtest.c-------------------
//APP 代码 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int fd;
	int buf = 233;
	
	fd = open("/dev/mychr0",O_RDWR);
	
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}
	
	while(1){
		buf = 0;
		write(fd, &buf, 4);
		sleep(1);
		
		buf = 1;
		write(fd, &buf, 4);
		sleep(1);
	}
		
	read(fd, &buf, 4);
		
	close(fd);

	return 0;
}

Makefile代码

ROOTFS_DIR = /home/linux/Level11

APP_NAME = drvtest

CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE), )

KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79
CUR_DIR = $(shell pwd)

all :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
		$(CC) $(APP_NAME).c -o $(APP_NAME)

clean :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) clean

install:
		cp -raf  *.ko  $(APP_NAME)   $(ROOTFS_DIR)/drv_module
		
else
obj-m += chr_drv.o

endif

效果展示:

[root@farsight ]# insmod chr_drv.ko
[  287.317368] register ok
[root@farsight ]# ./drvtest
[  291.632356] ------------chr_drv_open----------
[  291.635357] __KERN__:0
[  292.638023] __KERN__:1
[  293.639042] __KERN__:0
[  294.640062] __KERN__:1
[  295.641081] __KERN__:0
[  296.642106] __KERN__:1
[  297.643127] __KERN__:0
[  298.644145] __KERN__:1
[  299.645167] __KERN__:0
[  300.646187] __KERN__:1
[  301.647281] __KERN__:0
[  302.648296] __KERN__:1
[  302.732408] ------------chr_drv_close----------

开发板上灯一闪一灭。

各类型形参常用命名方式:
struct file * filp,    filp = file point ,文件指针
char __user *buf,    buf = buffer ,缓冲区
size_t count,  看到size就是指大小,个数
loff_t *fpos,  loff_t local offset 地址偏移,fpos=file position 文件位置

8. 驱动和应用程序的设计思想

应用程序和驱动扮演的是什么角色

用户态:应用程序
        玩策略: 怎么去做
                1, 一闪一闪
                2,10s闪一次,也可以1s闪一次
                3,一直亮
                4,跑马灯
        控制权是在应用程序(程序员)
--------------------------------------
内核态:驱动
        玩机制: 能做什么 
                led:亮 和 灭

9,编写字符设备驱动到步骤和规范

步骤:
1,实现模块加载和卸载入口函数
module_init(chr_dev_init);
module_exit(chr_dev_exit);

    2,在模块加载入口函数中
            a, 申请主设备号  (内核中用于区分和管理不同字符设备)
                                    register_chrdev(dev_major, "chr_dev_test", &my_fops);

            b,创建设备节点文件 (为用户提供一个可操作到文件接口--open())
                            struct  class *class_create(THIS_MODULE, "chr_cls");
                            struct  device *device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");

            c, 硬件的初始化
                            1,地址的映射
                                    gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);
                            2,中断到申请
                            3,实现硬件的寄存器到初始化
                                    // 需要配置gpio功能为输出
                                    *gpx2conf &= ~(0xf<<28);
                                    *gpx2conf |= (0x1<<28);
            e,实现file_operations
                            const struct file_operations my_fops = {
                                            .open = chr_drv_open,
                                            .read = chr_drv_read,
                                            .write = chr_drv_write,
                                            .release = chr_drv_close,
                            };

规范:
1,面向对象编程思想
用一个结构体来表示一个对象

            //设计一个类型,描述一个设备的信息
            struct led_desc{
                    unsigned int dev_major; //设备号
                    struct class *cls;
                    struct device *dev; //创建设备文件
                    void *reg_virt_base;
            };

            struct led_desc *led_dev;//表示一个全局的设备对象

            
            // 0, 实例化全局的设备对象--分配空间
            //  GFP_KERNEL 如果当前内存不够用到时候,该函数会一直阻塞(休眠)
            //  #include <linux/slab.h>
            led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
            if(led_dev == NULL)
            {
                    printk(KERN_ERR "malloc error\n");
                    return -ENOMEM;
            }

            led_dev->dev_major = 250;
    2,做出错处理
            在某个位置出错了,要将之前申请到资源进行释放
    
            led_dev = kmalloc(sizeof(struct led_desc), GFP_KERNEL);	
    
            led_dev->dev_major = register_chrdev(0, "led_dev_test", &my_fops);
            if(led_dev->dev_major < 0)
            {
                    printk(KERN_ERR "register_chrdev error\n");
                    ret = -ENODEV;
                    goto err_0;
            }


            err_0:
                    kfree(led_dev);
                    return ret;

10. 操作寄存器地址到方式:

1, volatile unsigned long *gpxcon;
	*gpxcon &= ~(0xf<<28);

2, readl/writel();
	u32 readl(const volatile void __iomem *addr)//从地址中读取地址空间到值

	void writel(unsigned long value , const volatile void __iomem *add)
		// 将value的值写入到addr地址

	例子:
		// gpio的输出功能的配置
		u32 value = readl(led_dev->reg_virt_base);
		value &= ~(0xf<<28);
		value |= (0x1<<28)
		writel(value, led_dev->reg_virt_bas);	

	或者:
			*gpx2dat |= (1<<7);
		替换成:
			writel( readl(led_dev->reg_virt_base + 4) | (1<<7),   led_dev->reg_virt_base + 4 );

对应错误代码

代码示例

//------------------led_drv.c------------------
//驱动程序代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>

#include <asm/io.h>
#include <linux/slab.h>
#include <asm/string.h>



#define GPX2CON_ADDR 0x11000c40
#define GPX2_SIZE 8

const char led_dev_name[] = "led_dev";

struct led_desc {
	unsigned int dev_major;		//主设备号
	unsigned int dev_minor;		//次设备号
	struct class *cls;			
	struct device* dev;			//创建设备文件
	void *reg_virt_base;		//表示是寄存器地址到基准值
};
struct led_desc *led_dev;		//表示一个全局的设备对象

int chr_drv_open (struct inode *inode, struct file *filp)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}
ssize_t chr_drv_read (struct file * filp, char __user *buf, size_t count, loff_t *fpos)
{
	int buf_read = 666;
	int ret;
	
	ret = copy_to_user(buf,&buf_read,count);
	if(ret == 0)
	{
		printk("------------%s----------\n",__FUNCTION__);
	}
	else 
	{
		printk("KERN:Error to read\n");
		return -EFAULT;
	}
	return 0;
}

ssize_t chr_drv_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	int buf_write = 0;
	int ret; 
	unsigned int value;
	
	ret = copy_from_user(&buf_write,buf,count);

	if(ret == 0)
	{
		printk("__KERN__:%d\n",buf_write);
		if(buf_write == 0){	
			value = readl((led_dev->reg_virt_base + 4));
			value &= ~(0x1<<7);
			writel(value,led_dev->reg_virt_base + 4);
		}
		else{
			value = readl((led_dev->reg_virt_base + 4));
			value |= (0x1<<7);
			writel(value,led_dev->reg_virt_base + 4);
		}
	}
	else 
	{
		printk("KERN:Error to read\n");
		return -EFAULT;
	}
	return 0;
}

int chr_drv_close (struct inode *inode, struct file *filp)
{
	printk("------------%s----------\n",__FUNCTION__);
	return 0;
}

const struct file_operations major_fops = {
		.open = chr_drv_open,
		.read = chr_drv_read,
		.write = chr_drv_write,
		.release = chr_drv_close,
	};

static int __init my_chrdrv_init(void)
{
	long ret;
	unsigned int value;
	//0. 实例化全局的设备对象--分配空间
	//  GFP_KERNEL 如果当前内存不够用到时候,该函数会一直阻塞(休眠)
	led_dev = (struct led_desc *)kmalloc(sizeof(struct led_desc),GFP_KERNEL);
		
	if(led_dev == NULL){
		printk(KERN_ERR "malloc error\n");
		return -ENOMEM;
	}
	
	memset((void*)led_dev,1,sizeof(struct led_desc));		//初始化led_dev
	
	//一般都是申请资源
	//申请设备号
	led_dev->dev_major = register_chrdev(0, led_dev_name, &major_fops);
	led_dev->dev_minor = 0;

	if (led_dev->dev_major < 0){
		printk(KERN_ERR "register failed\n");
		goto err0;
	}

	// 2,创建设备文件
	led_dev->cls = class_create(THIS_MODULE,"led_cls");

	//IS_ERR 用于内核判断指针合法性,包括指针是否为NULL,以及指针访问非法地址
	//合法为0 非法不为0
	if(IS_ERR(led_dev->cls))		
	{
		printk(KERN_ERR "class create failed\n");
		ret = PTR_ERR(led_dev->cls); //将指针出错的具体原因转换成一个出错码
		goto err1;
	}

	// /dev/led0
	//下面的"mychr%d",0 等效于 "mychr0"
	led_dev->dev = device_create(led_dev->cls,NULL,MKDEV(led_dev->dev_major, led_dev->dev_minor),NULL,"myled%d",0);
	
	if(IS_ERR(led_dev->dev))
	{
		printk(KERN_ERR "devic create failed\n");
		ret = PTR_ERR(led_dev->dev); //将指针出错的具体原因转换成一个出错码
		goto err2;
	}

	// 3,硬件初始化
	// 对地址进行映射
	led_dev->reg_virt_base = ioremap(GPX2CON_ADDR, GPX2_SIZE);	
	//下面做判断也可以用IS_ERR()
	if(led_dev->reg_virt_base == NULL)
	{
		printk(KERN_ERR "ioremap error\n");
		ret = -ENOMEM;
		goto err3;
	}

	// gpio的输出功能的配置
	value = readl(led_dev->reg_virt_base);
	value &= ~(0xf << 28);
	value |= (0x1 << 28);
	writel(value,led_dev->reg_virt_base);
	
	return 0;

err3:
	device_destroy(led_dev->cls,MKDEV(led_dev->dev_major, led_dev->dev_minor));
	
err2:
	class_destroy(led_dev->cls);

err1:
	unregister_chrdev(led_dev->dev_major, led_dev_name);

err0:
	kfree((void*)led_dev);
	return -ENOMEM;
}

static void __exit my_chrdrv_exit(void)
{
	//一般都是释放资源
	//释放设备号资源	
	iounmap(led_dev->reg_virt_base);
	device_destroy(led_dev->cls,MKDEV(led_dev->dev_major, led_dev->dev_minor));
	class_destroy(led_dev->cls);
	unregister_chrdev(led_dev->dev_major, led_dev_name);
	kfree((void*)led_dev);
}

MODULE_LICENSE("GPL");

module_init(my_chrdrv_init);
module_exit(my_chrdrv_exit);
//--------------led_test.c-------------------
//APP 代码 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
	int fd;
	int buf = 233;
	
	fd = open("/dev/myled0",O_RDWR);
	
	if(fd < 0)
	{
		perror("open");
		exit(1);
	}
	
	while(1){
		buf = 0;
		write(fd, &buf, 4);
		sleep(1);
		
		buf = 1;
		write(fd, &buf, 4);
		sleep(1);
	}
		
	read(fd, &buf, 4);
		
	close(fd);

	return 0;
}
#Makefile代码
ROOTFS_DIR = /home/linux/Level11

APP_NAME = led_test

CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc

ifeq ($(KERNELRELEASE), )

KERNEL_DIR = /home/linux/Level10/day6/linux-5.4.79
CUR_DIR = $(shell pwd)

all :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) modules -j6 ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi-
		$(CC) $(APP_NAME).c -o $(APP_NAME)

clean :
		make -C  $(KERNEL_DIR) M=$(CUR_DIR) clean
		clear

install:
		cp -raf  *.ko  $(APP_NAME)   $(ROOTFS_DIR)/drv_module
		
else
obj-m += led_drv.o

endif

效果展示

[root@farsight ]# insmod led_drv.ko
[root@farsight ]# ./led_test
[ 1781.874456] ------------chr_drv_open----------
[ 1781.877516] __KERN__:0
[ 1782.880055] __KERN__:1
[ 1783.881074] __KERN__:0
[ 1784.882093] __KERN__:1
[ 1785.883115] __KERN__:0
[ 1786.884136] __KERN__:1
[ 1787.384514] ------------chr_drv_close----------
[root@farsight ]# rmmod led_drv.ko

灯一闪一闪,效果达成。