T4 Linux字符设备驱动开发

1.驱动分类

1.1字符设备

1.1.1特点

  • 数据为字符流,数据从寄存器产生
  • 传输数据少而快
  • 如LCD屏,keyboard,IIC等

1.1.2上层调用方式

  • 上层应用以文件描述符形式打开驱动,如open,read,write
  • 上层每调用某函数,在驱动层面都有相应接口函数,如上层open对于底层xxx_open
  • 为方便上层应用程序寻找底层驱动,区分驱动类型,因此引入设备号的概念.类似于身份证号码
  • 驱动会对外提供自己的名称与设备号,名称在一个系统里面不唯一,但是设备号却是唯一的
  • 例如某led驱动,其名称为/dev/leds(名称叫设备节点),还有设备号
  • 即linux将一切设备当成文件,假如上层通过open打开某led驱动,那么对于底层驱动就会有对应led_open函数来操作硬件

1.2块设备

1.2.1特点

  • 数据以块为单位
  • 对于一些存储设备,例如flash,磁盘
  • 传输数据大而慢

1.3网络设备

1.3.1特点

  • 涉及协议等,同一由socket管理

2.申请设备号

2.1概念

  • 设备号类似身份证,用于区分驱动
  • 其本质就是一个32位数据
  • 设备号分为主设备号(高12bit)与次设备号(低20bit)
  • 不同类型设备设备号独立,如字符设备里面的设备号1与块设备里面的设备号1不同
  • 主设备号表示一类设备,例如摄像头
  • 次设备号表示一类设备中的某一个设备,例如前置摄像头与后置摄像头
  • 例如
hanqi@hanqi-PC:/dev$ ls -l
                       # 主设备号 次设备号
crw-rw-rw-  1 root tty       4,   0 8月  30 08:30 tty0
crw-rw-rw-  1 root tty       4,   1 8月  30 08:30 tty1
crw-rw-rw-  1 root tty       4,  10 8月  30 08:30 tty10
crw-rw-rw-  1 root tty       4,  11 8月  30 08:30 tty11
crw-rw-rw-  1 root tty       4,  12 8月  30 08:30 tty12
crw-rw-rw-  1 root tty       4,  13 8月  30 08:30 tty13

2.2申请设备号API

  • register_chrdev申请设备号
#include 
/*
功能:申请设备号
参数:
参数1:主设备号,(0表示由系统动态分配;也可以静态指定一个整数,注意不要与已经存在设备号冲突)
参数2:描述设备信息,在/proc/devices目录下列出所有已经注册的设备
参数3:文件操作对象,提供open,read,write等
返回值:正确返回0,失败返回负数
*/

int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops);
    
/*
功能:释放设备号
参数:
参数1:主设备号,(0表示由系统动态分配;也可以静态指定一个整数,注意不要与已经存在设备号冲突)
参数2:描述设备信息,在/proc/devices目录下列出所有已经注册的设备
返回值:无
*/
    
void unregister_chrdev(unsigned int major, const char * name);

2.3示例代码

#include 
#include 
#include 

/*静态指定主设备号*/
static unsigned int dev_major = 77;
const struct file_operations my_fop = {
};
static int __init chr_dev_init(void)
{
    int ret;
    /*申请设备号*/
    ret = register_chrdev(dev_major, "chr_dev_test", &my_fop);
    if(ret == 0){
        printk("register ok\n");
    }else{
        printk("register failed\n");
        return -EINVAL;
    }
    return 0;
}
static void __exit chr_dev_exit(void)
{
    /*卸载设备号*/
    unregister_chrdev(dev_major, "chr_dev_test");
}

module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
  • 首先查看系统里面已经申请的设备号cat /proc/devices
pi@raspberrypi:~/my_drivers $ cat /proc/devices
# 已经申请的字符设备号
Character devices:
  1 mem
  4 /dev/vc/0
  4 tty
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
  5 ttyprintk
  7 vcs
 10 misc
 13 input
 29 fb
 89 i2c
116 alsa
128 ptm
136 pts
162 raw
180 usb
189 usb_device
204 ttyAMA
244 uio
245 vchiq
246 vcsm
247 hidraw
248 bcm2835-gpiomem
249 vcio
250 vc-mem
251 bsg
252 watchdog
253 rtc
254 gpiochip
# 已经申请的块设备号
Block devices:
  1 ramdisk
  7 loop
  8 sd
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
259 blkext
  • 装载chr_drv.ko
pi@raspberrypi:~/my_drivers $ cat /proc/devices      
Character devices:
 ...
 77 chr_dev_test    # 自己添加的
 ...

Block devices:
 ...
# 查看打印信息
pi@raspberrypi:~/my_drivers $ dmesg
[  618.430028] register ok

3.创建设备节点

3.1创建方式

3.1.1手动创建

  • 手动创建mknode /dev/设备名 类型 主设备号 次设备号,例如mknode /dev/chr0 c 77 0
# 创建设备节点
pi@raspberrypi:~/my_drivers $ sudo mknod /dev/chr0 c 77 0
# 查看是否创建成功
pi@raspberrypi:~/my_drivers $ ls /dev/chr0 -l            
crw-r--r-- 1 root root 77, 0 Aug 30 10:55 /dev/chr0
  • 手动创建缺点:/dev/xx目录文件存在于内存里面,断电之后所创建的节点将会消失

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

  • API
#include 

/*
功能:创建一个类
参数:
参数1:一般写为THIS_MODULE
参数2:字符串名字,自己写
返回值:class结构体类型指针
*/
struct class *class_create(owner, name);

/*
功能:销毁一个创建的类
参数:class_create创建的class结构体类型指针
*/
class_destroy(struct class * cls);
-----------------------------------------------------------------------------------------------------
/*
功能:创建一个设备文件
参数:
参数1:class_create创建的class结构体类型指针
参数2:父亲,一般直接写NULL
参数3:设备号,通过MDKEV()宏创建
参数4:私有数据,一般写NULL
参数5,6:可变参数,一般为字符串,表示设备节点名字
返回值:device类型指针
*/
struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt, ...);

/*
功能:销毁一个创建的设备文件
参数:
参数1:class_create创建的class结构体类型指针
参数2:设备号,通过MDKEV()宏创建
*/
device_destroy(struct class * class, dev_t devt);
-----------------------------------------------------------------------------------------------------
/*
功能:通过主设备号与次设备号合成一个设备号
参数:
参数1:主设备号
参数2:次设备号
返回值:设备号
*/
#define MKDEV(ma, mi) ((ma) << 20 | (mi))

3.1.3自动创建示例代码

  • 代码,注意:入口函数里面的申请资源的函数顺序与释放资源函数里面的函数执行顺序相反
  • 例如,初始化顺序为register_chrdev,class_create,device_create;释放资源顺序为device_destroy,class_destroy,unregister_chrdev
#include 
#include 
#include 
#include 

/*静态指定主设备号*/
static unsigned int dev_major = 77;
static struct class *devcls;
static struct device *dev;
const struct file_operations my_fop = {
};
static int __init chr_dev_init(void)
{
    int ret;
    /*申请设备号*/
    ret = register_chrdev(dev_major, "chr_dev_test", &my_fop);
    if(ret == 0){
        printk("register ok\n");
    }else{
        printk("register failed\n");
        return -EINVAL;
    }
    devcls = class_create(THIS_MODULE, "chr_cls");
    dev = device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");
    return 0;
}
static void __exit chr_dev_exit(void)
{
    device_destroy(devcls, MKDEV(dev_major, 0));
    class_destroy(devcls);
    /*卸载设备号*/
    unregister_chrdev(dev_major, "chr_dev_test");
}
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
  • 现象,装载驱动后可看到在/dev下创建了一个chr2的设备节点,卸载后节点消失
pi@raspberrypi:~/my_drivers $ sudo insmod chr_drv.ko
pi@raspberrypi:~/my_drivers $ ls /dev/chr2
/dev/chr2
pi@raspberrypi:~/my_drivers $ sudo rmmod chr_drv 
pi@raspberrypi:~/my_drivers $ ls /dev/chr2       
ls: cannot access '/dev/chr2': No such file or directory

4.用户层调用内核层

4.1在驱动内实现文件IO操作接口

  • 文件操作对象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 (*readdir) (struct file *, void *, filldir_t);
        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);
};
  • 驱动示例代码,模拟实现open,close,read,write函数
#include 
#include 
#include 
#include 

/*静态指定主设备号*/
static unsigned int dev_major = 77;
static struct class *devcls;
static struct device *dev;

/*驱动实现的接口函数*/
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 *fops)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}
int chr_drv_open (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}
int che_dev_close (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

const struct file_operations my_fop = {
    .open = chr_drv_open,
    .read = chr_drv_read,
    .write = chr_drv_write,
    .release = che_dev_close,
};
static int __init chr_dev_init(void)
{
    int ret;
    /*申请设备号*/
    ret = register_chrdev(dev_major, "chr_dev_test", &my_fop);
    if(ret == 0){
        printk("register ok\n");
    }else{
        printk("register failed\n");
        return -EINVAL;
    }
    devcls = class_create(THIS_MODULE, "chr_cls");
    dev = device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");
    return 0;
}
static void __exit chr_dev_exit(void)
{
    device_destroy(devcls, MKDEV(dev_major, 0));
    class_destroy(devcls);
    /*卸载设备号*/
    unregister_chrdev(dev_major, "chr_dev_test");
    
}
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
  • 上层调用代码
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    /*调用底层对应驱动*/
    int fd;
    int value = 0;
    fd = open("/dev/chr2", O_RDWR);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    read(fd, &value, 4);
    write(fd, &value, 4);
    close(fd);
    return 0;   
}
  • 修改Makefile,使用开发板的交叉编译工具链编译测试代码
ROOTFS_DIR = /home/hanqi/my_drivers_ko
# 应用程序名称
APP_NAME = chr_test
# 添加交叉编译工具链
CROSS_COMPILE = /home/hanqi/pi/toolchain/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
ifeq ($(KERNELRELEASE), )
KERNEL_DIR = /home/hanqi/pi/linux-rpi-4.14.y
CUR_DIR = $(shell pwd)

all:
    make -C $(KERNEL_DIR) M=$(CUR_DIR) modules ARCH=arm CROSS_COMPILE=/home/hanqi/pi/toolchain/tools-master/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
    $(CC) $(APP_NAME).c -o $(APP_NAME)
clean:
    make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
install:
    cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)
else
obj-m += chr_drv.o
endif

  • 执行结果,可见上层调用open对应在驱动里面调用chr_drv_open
# 装载驱动
pi@raspberrypi:~/my_drivers $ sudo insmod chr_drv.ko
# 查看设备节点是否创建成功
pi@raspberrypi:~/my_drivers $ ls /dev/chr2
/dev/chr2
# 运行应用程序
pi@raspberrypi:~/my_drivers $ sudo ./chr_test 
# 查看运行结果
pi@raspberrypi:~/my_drivers $ dmesg
...
[  163.633648] chr_drv: loading out-of-tree module taints kernel.
[  163.634296] register ok
[  254.902358] ------chr_drv_open------
[  254.902378] ------chr_drv_read------
[  254.902389] ------chr_drv_write------
[  254.902401] ------che_dev_close------

5.上层应用与内核的数据交互

5.1介绍

  • 如果要控制底层硬件,那么上层用户就需要传参给驱动
  • ssize_t chr_drv_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)中的buf对应用户空间的buff,那么是否可以在内核空间内部直接将其解引用从而将内核空间数据直接给用户空间呢?理论上可以,但不建议使用该方法.因为假如用户空间的buff为一个空指针,那么在内核赋值的时候就会对空指针解引用*null,造成内核出错
  • 为解决上述问题,内核提供2个API供用户与内核交互数据

5.2APP与Driver相互传参API

  • copy_to_user函数,一般用于驱动内部read接口函数
#include 
#include 
/*
功能:将数据从内核空间拷贝到用户空间
参数:
参数1:应用空间的缓冲区
参数2:内核驱动中的缓冲区
参数3:数据大小
返回值:大于0表示错误(即未被拷贝的数据),等于0表示正确(没剩余,拷贝成功)
*/

int copy_to_user(void __user *to, const void *from, unsigned long n);
  • copy_from_user函数,一般用于驱动内部write接口函数
#include 
#include 
/*
功能:从用户空间拷贝数据到内核空间
参数:
参数1:内核驱动中的缓冲区
参数2:应用空间的缓冲区
参数3:数据大小
返回值:大于0表示错误(即未被拷贝的数据),等于0表示正确(没剩余,拷贝成功)
*/

int copy_from_user(void *to, const void *from, unsigned long n);

5.3代码示例

  • 内核驱动代码
#include 
#include 
#include 
#include 
#include 


/*静态指定主设备号*/
static unsigned int dev_major = 77;
static struct class *devcls;
static struct device *dev;

/*内核空间数据*/
static int kernel_val = 555;
/*驱动实现的接口函数*/
ssize_t chr_drv_read (struct file *filp, char __user *buff, size_t count, loff_t *fpos)
{
    int ret;
    printk("------%s------\n", __FUNCTION__);
    /*将kernel_val(555)数据给用户空间*/
    ret = copy_to_user(buff, &kernel_val, count);
    if(ret > 0)
    {
        printk("copy to user err\n");
        return -EFAULT;
    }
    return 0;
}
ssize_t chr_drv_write (struct file *filp, const char __user *buff, size_t count, loff_t *fops)
{
    int ret;
    printk("------%s------\n", __FUNCTION__);
    /*从用户空间接收数据给内核空间(kernel_val)*/
    ret = copy_from_user(&kernel_val, buff, count);
    if(ret > 0)
    {
        printk("copy from user err\n");
        return -EFAULT;
    }
    /*打印从用户空间接收的数据*/
    printk("__KERNEL__: value = %d\n", kernel_val);
    return 0;
}
int chr_drv_open (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}
int che_dev_close (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

const struct file_operations my_fop = {
    .open = chr_drv_open,
    .read = chr_drv_read,
    .write = chr_drv_write,
    .release = che_dev_close,
};
static int __init chr_dev_init(void)
{
    int ret;
    /*申请设备号*/
    ret = register_chrdev(dev_major, "chr_dev_test", &my_fop);
    if(ret == 0){
        printk("register ok\n");
    }else{
        printk("register failed\n");
        return -EINVAL;
    }
    devcls = class_create(THIS_MODULE, "chr_cls");
    dev = device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");
    return 0;
}
static void __exit chr_dev_exit(void)
{
    device_destroy(devcls, MKDEV(dev_major, 0));
    class_destroy(devcls);
    /*卸载设备号*/
    unregister_chrdev(dev_major, "chr_dev_test");
    
}
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
  • 用户空间代码
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    /*调用底层对应驱动*/
    int fd;
    int value = 0;
    fd = open("/dev/chr2", O_RDWR);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    read(fd, &value, 4);
    /*打印从内核空间取到的值*/
    printf("___USER___: value = %d\n", value);
    value = 666;
    /*将更改后的数据给内核空间*/
    write(fd, &value, 4);
    close(fd);
    return 0;   
}
  • 执行结果,可看到用户接收到内核的555,内核接收到用户的666
pi@raspberrypi:~/my_drivers $ sudo insmod chr_drv.ko 
pi@raspberrypi:~/my_drivers $ sudo ./chr_test 
___USER___: value = 555
pi@raspberrypi:~/my_drivers $ dmesg
...
[ 3627.450490] register ok
[ 3662.757692] ------chr_drv_open------
[ 3662.757713] ------chr_drv_read------
[ 3662.758097] ------chr_drv_write------
[ 3662.758106] __KERNEL__: value = 666
[ 3662.758117] ------che_dev_close------

6.上层应用控制硬件

6.1介绍

6.1.1概述

  • 上层操作驱动,驱动操作硬件
  • 驱动通过CPU寄存器控制硬件
  • 由于内核有MMU(内存管理单元),MMU将硬件地址屏蔽掉了
  • MMU将硬件物理地址(PMA)转换为虚拟地址(VMA)
  • 所以在驱动里面不是直接操作CPU寄存器地址,而是操作虚拟地址.通过ioremap函数将物理地址转换为虚拟地址

6.1.2地址总线

  • 即CUP的可寻址范围,例如某系统是32位,那么其可寻址范围是2^32,即4G.故假如系统为32位版本,那么即使装8G内存条,其可以的也就4G左右

6.1.3物理地址

  • 硬件的实际地址

6.1.4虚拟地址

  • 转换后的物理地址,假如物理地址有1G,通过MMU映射之后会被映射为4G,这样保证了程序的运行

6.2地址转换API

  • ioremap函数
#include 
/*
功能:将物理地址映射为虚拟地址
参数:
参数1:要映射的起始的IO地址
参数2:要映射的空间的大小
返回值:虚拟地址
*/

void *ioremap(cookie, size)
  • iounmap函数
#include 
/*
功能:解除地址映射关系
参数:映射之后的虚拟地址
返回值:无
*/

void iounmap(void __iomem *addr)

6.3驱动点灯

6.3.1芯片手册阅读

6.3.1.1Exynos4412寄存器

  • 首先找到要操作的寄存器物理地址,一般某寄存器的物理地址等于基地址加上偏移地址
  • GPX2CON配置寄存器用于配置第2组GPIO的功能与方向,其基地址为0x1100 0000,偏移地址为0x0C40,故其物理地址为0x1100 0C40
  • GPX2DAT数据寄存器用于配置GPIO口高低电平,其基地址为0x1100 0000,偏移地址为0x0C44,故其物理地址为0x1100 0C44
  • 以上分析的为寄存器物理地址,在驱动中需要将其转换为虚拟地址.针对配置寄存器而言,将会对其[31:28]四位进行配置,将其配置为输出状态;针对数据寄存器,对其第7位操作即可
  • 驱动代码
#include 
#include 
#include 
#include 
#include 

/*寄存器物理地址宏定义*/
#define GPX2_CON 0x11000C40
#define GPX2_SIZE 8

/*存放虚拟地址的指针,4Byte*/
volatile unsigned long *gpx2conf;
volatile unsigned long *gpx2data;

/*静态指定主设备号*/
static unsigned int dev_major = 77;
static struct class *devcls;
static struct device *dev;

/*内核空间数据*/
static int kernel_val = 555;
/*驱动实现的接口函数*/
ssize_t chr_drv_read (struct file *filp, char __user *buff, size_t count, loff_t *fpos)
{
    int ret;
    printk("------%s------\n", __FUNCTION__);
    /*将kernel_val(555)数据给用户空间*/
    ret = copy_to_user(buff, &kernel_val, count);
    if(ret > 0)
    {
        printk("copy to user err\n");
        return -EFAULT;
    }
    return 0;
}
ssize_t chr_drv_write (struct file *filp, const char __user *buff, size_t count, loff_t *fops)
{
    int ret;
    /*从用户空间接收数据给内核空间(kernel_val)*/
    ret = copy_from_user(&kernel_val, buff, count);
    if(ret > 0)
    {
        printk("copy from user err\n");
        return -EFAULT;
    }
    /*对用户空间接收的数据进行判断*/
    if(kernel_val){
        /*开灯*/
        *gpx2data |= 1<<7;
    }else{
        /*关灯*/
        *gpx2data &= ~(1<<7);
    }
    return 0;
}
int chr_drv_open (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}
int che_dev_close (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

const struct file_operations my_fop = {
    .open = chr_drv_open,
    .read = chr_drv_read,
    .write = chr_drv_write,
    .release = che_dev_close,
};
static int __init chr_dev_init(void)
{
    int ret;
    /*申请设备号*/
    ret = register_chrdev(dev_major, "chr_dev_test", &my_fop);
    if(ret == 0){
        printk("register ok\n");
    }else{
        printk("register failed\n");
        return -EINVAL;
    }
    devcls = class_create(THIS_MODULE, "chr_cls");
    dev = device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");

    /*映射物理地址*/
    gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);
    /*配置寄存器直接偏移4Byte为数据寄存器*/
    gpx2data = gpx2conf + 1;
    /*配置引脚为输出*/
    *gpx2conf &= ~(0xf<<28);
    *gpx2conf |= (0x1<<28);
    return 0;
}
static void __exit chr_dev_exit(void)
{
    iounmap(gpx2conf);
    device_destroy(devcls, MKDEV(dev_major, 0));
    class_destroy(devcls);
    /*卸载设备号*/
    unregister_chrdev(dev_major, "chr_dev_test");
    
}
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
  • 应用代码
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    /*调用底层对应驱动*/
    int fd;
    int value = 0;
    fd = open("/dev/chr2", O_RDWR);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    read(fd, &value, 4);
    /*打印从内核空间取到的值*/
    printf("___USER___: value = %d\n", value);
    /*APP控制灯*/
    while(1)
    {
        value = 0;
        write(fd, &value, 4);
        sleep(1);
        value = 1;
        write(fd, &value, 4);
        sleep(1);
    }
    close(fd);
    return 0;   
}

6.3.1.2树莓派寄存器

  • 树莓派3B芯片BCM2709的GPIO外设基地址为0x3f200000,与手册上不同,手册上为0x7E200000,手册上偏移地址是正确的
  • GPFSELx为树莓派引脚配置寄存器,偏移地址为0x0000,其中x属于[0,5],即6组引脚.前4组里面每组有10个引脚,第5组有8个引脚,在这里我们需要控制第4个引脚,其属于第0组的第[12,14]位
  • SETn为树莓派置位引脚,偏移地址为0x001C,写1表示输出高电平,写0表示无效
  • CLRn为树莓派清空引脚,偏移地址为0x0028,写1表示输出低电平,写0表示无效
  • 驱动代码
#include 
#include 
#include 
#include 
#include 
#include 


/*寄存器物理地址宏定义*/
#define GPFSEL0 0x3f200000
#define GPSET0 0x3f20001C
#define GPCLR0 0x3f200028

/*存放虚拟地址的指针,4Byte*/
volatile unsigned long *sel0 = NULL;
volatile unsigned long *set0 = NULL;
volatile unsigned long *clr0 = NULL;

/*静态指定主设备号*/
static unsigned int dev_major = 77;
static struct class *devcls;
static struct device *dev;

/*内核空间数据*/
static int kernel_val = 555;
/*驱动实现的接口函数*/
ssize_t chr_drv_read (struct file *filp, char __user *buff, size_t count, loff_t *fpos)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}
ssize_t chr_drv_write (struct file *filp, const char __user *buff, size_t count, loff_t *fops)
{
    int ret;
    /*从用户空间接收数据给内核空间(kernel_val)*/
    ret = copy_from_user(&kernel_val, buff, count);
    if(ret > 0)
    {
        printk("copy from user err\n");
        return -EFAULT;
    }
    /*对用户空间接收的数据进行判断*/
    if(kernel_val){
        /*开灯*/
        *set0 |= (0x1 << 4);
        printk("pin4 = 1\n");
    }else{
        /*关灯*/
        *clr0 |= (0x1 << 4);
        printk("pin4 = 0\n");
    }
    return 0;
}
int chr_drv_open (struct inode *inode, struct file *filp)
{
    printk("gpio init...\n");
    /*配置pin4为输出*/
    *sel0 &= ~(0x111 << 12);
    *sel0 |= (0x1 << 12);
    printk("gpio stat:out\n");
    return 0;
}
int che_dev_close (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

const struct file_operations my_fop = {
    .open = chr_drv_open,
    .read = chr_drv_read,
    .write = chr_drv_write,
    .release = che_dev_close,
};
static int __init chr_dev_init(void)
{
    int ret;
    /*申请设备号*/
    ret = register_chrdev(dev_major, "chr_dev_test", &my_fop);
    if(ret == 0){
        printk("register ok\n");
    }else{
        printk("register failed\n");
        return -EINVAL;
    }
    devcls = class_create(THIS_MODULE, "chr_cls");
    dev = device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");

    /*映射物理地址*/
    sel0 = (volatile unsigned long *)ioremap(GPFSEL0, 4);
    set0 = (volatile unsigned long *)ioremap(GPSET0, 4);
    clr0 = (volatile unsigned long *)ioremap(GPCLR0, 4);
    return 0;
}
static void __exit chr_dev_exit(void)
{
    iounmap(clr0);
    iounmap(set0);
    iounmap(sel0);
    device_destroy(devcls, MKDEV(dev_major, 0));
    class_destroy(devcls);
    /*卸载设备号*/
    unregister_chrdev(dev_major, "chr_dev_test");
    
}
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");
  • 应用代码
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    /*调用底层对应驱动*/
    int fd;
    int value = 0;
    fd = open("/dev/chr2", O_RDWR);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    /*APP控制灯*/
    while(1)
    {
        printf("pls input a cmd(0:off):");
        scanf("%d", &value);
        write(fd, &value, 4);
    }
    close(fd);
    return 0;   
}
  • 运行结果
pi@raspberrypi:~/my_drivers $ sudo insmod chr_drv.ko       
pi@raspberrypi:~/my_drivers $ sudo ./chr_test        
pls input a cmd(0:off):1
pls input a cmd(0:off):0
pls input a cmd(0:off):1

pi@raspberrypi:~/my_drivers $ dmesg
[  627.776964] register ok
[  644.229258] gpio init...
[  644.229268] gpio stat:out
[  648.566771] pin4 = 1
[  702.369055] pin4 = 0
[  719.650201] pin4 = 1
  • 查看pin4引脚状态
# 可看到BCM 4引脚为输出状态为高电平
pi@raspberrypi:~ $ gpio readall
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |  OUT | 1 |  7 || 8  | 1 | ALT0 | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT0 | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
# 可看到BCM 4引脚为输出状态为低电平
pi@raspberrypi:~ $ gpio readall
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |  OUT | 0 |  7 || 8  | 1 | ALT0 | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT0 | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
# 可看到BCM 4引脚为输出状态为高电平
pi@raspberrypi:~ $ gpio readall
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 |     |     |    3.3v |      |   |  1 || 2  |   |      | 5v      |     |     |
 |   2 |   8 |   SDA.1 |   IN | 1 |  3 || 4  |   |      | 5v      |     |     |
 |   3 |   9 |   SCL.1 |   IN | 1 |  5 || 6  |   |      | 0v      |     |     |
 |   4 |   7 | GPIO. 7 |  OUT | 1 |  7 || 8  | 1 | ALT0 | TxD     | 15  | 14  |
 |     |     |      0v |      |   |  9 || 10 | 1 | ALT0 | RxD     | 16  | 15  |
 |  17 |   0 | GPIO. 0 |   IN | 0 | 11 || 12 | 0 | IN   | GPIO. 1 | 1   | 18  |
 |  27 |   2 | GPIO. 2 |   IN | 0 | 13 || 14 |   |      | 0v      |     |     |
 |  22 |   3 | GPIO. 3 |   IN | 0 | 15 || 16 | 0 | IN   | GPIO. 4 | 4   | 23  |
 |     |     |    3.3v |      |   | 17 || 18 | 0 | IN   | GPIO. 5 | 5   | 24  |
 |  10 |  12 |    MOSI |   IN | 0 | 19 || 20 |   |      | 0v      |     |     |
 |   9 |  13 |    MISO |   IN | 0 | 21 || 22 | 0 | IN   | GPIO. 6 | 6   | 25  |
 |  11 |  14 |    SCLK |   IN | 0 | 23 || 24 | 1 | IN   | CE0     | 10  | 8   |
 |     |     |      0v |      |   | 25 || 26 | 1 | IN   | CE1     | 11  | 7   |
 |   0 |  30 |   SDA.0 |   IN | 1 | 27 || 28 | 1 | IN   | SCL.0   | 31  | 1   |
 |   5 |  21 | GPIO.21 |   IN | 1 | 29 || 30 |   |      | 0v      |     |     |
 |   6 |  22 | GPIO.22 |   IN | 1 | 31 || 32 | 0 | IN   | GPIO.26 | 26  | 12  |
 |  13 |  23 | GPIO.23 |   IN | 0 | 33 || 34 |   |      | 0v      |     |     |
 |  19 |  24 | GPIO.24 |   IN | 0 | 35 || 36 | 0 | IN   | GPIO.27 | 27  | 16  |
 |  26 |  25 | GPIO.25 |   IN | 0 | 37 || 38 | 0 | IN   | GPIO.28 | 28  | 20  |
 |     |     |      0v |      |   | 39 || 40 | 0 | IN   | GPIO.29 | 29  | 21  |
 +-----+-----+---------+------+---+----++----+---+------+---------+-----+-----+
 | BCM | wPi |   Name  | Mode | V | Physical | V | Mode | Name    | wPi | BCM |
 +-----+-----+---------+------+---+---Pi 3B--+---+------+---------+-----+-----+
  • 测试使用
//寄存器物理地址
volatile unsigned int *GPFSEL0 = volatile(unsigned int *)0x3f200000;
volatile unsigned int *GPSET0 = volatile(unsigned int *)0x3f20001C;
volatile unsigned int *GPCLR0 = volatile(unsigned int *)0x3f200028;
//寄存器虚拟地址
GPFSEL0 = volatile(unsigned int *)ioremap(0x3f200000, 4);
GPSET0 = volatile(unsigned int *)ioremap(0x3f20001C, 4);
GPCLR0 = volatile(unsigned int *)ioremap(0x3f200028, 4);

7.规范化编写驱动

7.1规范

  • 按照上述的方式虽然将功能实现出来,但是其编程方式并不规范。
  • 实际上编写驱动程序需要使用到面向对象的编程思想。在C语言中使用结构体表示一个对象,这样可以提升代码可移植性和可阅读性
  • 还需要实现对应的出错机制

及时释放分配的内存资源,防止内存泄漏(通过goto实现)

7.2规范化Makefile

  • 修改后的makefile,一个make可同时编译驱动与应用程序
ROOTFS_DIR = /home/hanqi/my_drivers_ko

# 应用程序代码名称
APP_NAME = chr_test
# 驱动代码名称
MODULE_NAME = chr_drv

CROSS_COMPILE = /home/hanqi/pi/toolchain/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-
CC = $(CROSS_COMPILE)gcc
ifeq ($(KERNELRELEASE), )
KERNEL_DIR = /home/hanqi/pi/linux-rpi-4.14.y
CUR_DIR = $(shell pwd)

all:
    make -C $(KERNEL_DIR) M=$(CUR_DIR) modules ARCH=arm CROSS_COMPILE=/home/hanqi/pi/toolchain/tools-master/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
    $(CC) $(APP_NAME).c -o $(APP_NAME)
clean:
    make -C $(KERNEL_DIR) M=$(CUR_DIR) clean
    rm $(APP_NAME)
install:
    cp -raf *.ko $(APP_NAME) $(ROOTFS_DIR)
else
obj-m += $(MODULE_NAME).o
endif

7.3Exynos4412驱动规范化

  • 内核内存分配API
#include 

/*
功能:内核中来为某变量分配空间
参数:
参数1:要分配的块的大小
参数2:分配标志
    GFP_KERNEL:内核内存的通常分配方法,可能会引起休眠。即如果当前内存不够用的时候,该函数会一直阻塞
    GFP_USER:用于为用户空间分配内存,可能会休眠。
    GFP_ATOMIC:用于在中断处理例程或其他运行于进程上下文之外的代码中分配内存,不会休眠。
    GFP_HIGHUSER:用于为用户空间分配内存,指高端内存分配,可能会休眠。
    GFP_NOIO:禁止任何I/O的初始化(主要在虚拟内存代码中使用)。
    GFP_NOFS:分配不允许执行任何文件系统调用(主要在文件系统代码中使用)。
****************************************************************
分割线以上的flag可以和分割线以下的flag “或”起来使用
****************************************************************
    __GFP_DMA:该标志请求分配发生在可进行DMA的内存区段中。
    __GFP_HIGHMEM:该标志表明要分配的内存可位于高端内存。
    __GFP_NOWAPN:该标志使用的次数较少,它主要是避免内核在无法满足分配请求时产生警告信息。
    __GFP_COLD:该标志表示请求尚未使用的“冷”页面。
    __GFP_HIGH:该标记标记了一个高优先级的请求,它允许为紧急状况而消耗由内核保留的最后一些页面。
    __GFP_REPEAT:该标志表示在分配器在满足分配请求而遇到困难时,“努力再尝试一次”,它会重新尝试分配,但还是有失败的可能性。
    __GFP_NOFAIL:该标志表示在分配器在满足分配请求而遇到困难时告诉分配器始终不返回失败。
    __GFP_NORETRY:该标志表示再请求内存不可获得的时候会立即返回。
    
    GFP_前缀是由于在分配内存时总是调用get_free_page来实现实际的分配而得来的缩写。
返回值:分配内存的首地址
注意:用户空间malloc是基于堆内存分配,内核负责管理系统物理内存,物理内存只能按页面进行分配,因此,kmalloc是基于页进行分配。另外需要注意的一点是内核只能分配一些预定义的、固定大小的字节数组。kmalloc 可以处理的最小的内存块是32或64,最大分配的内存大小为128K。
*/
void *kmalloc(size_t size, int flags);
  • 操作地址API
#include  
/*
功能:向某个地址中写入值
参数:
参数1:要写入的数据
参数2:写入数据的地址
返回值:无
*/

void writel (unsigned char data , unsigned short addr )

/*
功能:从某个地址中读取地址空间的值
参数:
参数1:要读取的地址
返回值:读取的地址,u32类型
*/
unsigned char readl (unsigned int addr )
  • 面向对象思想,在C中使用结构体来表现一个对象
  • 出错处理
  • 改进后
#include 
#include 
#include 
#include 
#include 


/*设计一个类,用来描述设备信息*/
struct chr_desc
{
    unsigned int dev_major;         //描述设备号
    struct class *cls;
    struct device *dev;             //创建设备文件
    void *reg_virt_base;            //虚拟内存基地址
};

/*寄存器物理地址宏定义*/
#define GPX2_CON 0x11000C40
#define GPX2_SIZE 8

/*声明一个全局设备对象,用于描述整个设备信息*/
struct chr_desc *chr_dev;

/*内核空间数据*/
static int kernel_val = 555;
/*驱动实现的接口函数*/
ssize_t chr_drv_read (struct file *filp, char __user *buff, size_t count, loff_t *fpos)
{
    int ret;
    printk("------%s------\n", __FUNCTION__);
    /*将kernel_val(555)数据给用户空间*/
    ret = copy_to_user(buff, &kernel_val, count);
    if(ret > 0)
    {
        printk("copy to user err\n");
        return -EFAULT;
    }
    return 0;
}
ssize_t chr_drv_write (struct file *filp, const char __user *buff, size_t count, loff_t *fops)
{
    int ret;
    /*从用户空间接收数据给内核空间(kernel_val)*/
    ret = copy_from_user(&kernel_val, buff, count);
    if(ret > 0)
    {
        printk("copy from user err\n");
        return -EFAULT;
    }
    /*对用户空间接收的数据进行判断*/
    if(kernel_val){
        /*开灯*/
        writel(readl(chr_dev->reg_virt_base + 4) | (1<<7), chr_dev->reg_virt_base + 4);
    }else{
        /*关灯*/
        writel(readl(chr_dev->reg_virt_base + 4) & ~(1<<7), chr_dev->reg_virt_base + 4);
    }
    return 0;
}
int chr_drv_open (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}
int che_dev_close (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}

const struct file_operations my_fop = {
    .open = chr_drv_open,
    .read = chr_drv_read,
    .write = chr_drv_write,
    .release = che_dev_close,
};
static int __init chr_dev_init(void)
{
    int ret;
    /*实例化全局设备对象(分配空间)*/
    chr_dev = kmalloc(sizeof(struct chr_desc), GFP_KERNEL);
    if(chr_dev == NULL)
    {
        /*打印调试信息,KERN_ERR等系列标签的使用方便用户对调试信息按等级进行筛选*/
        printk(KERN_ERR "malloc error\n");
        return -ENOMEM;
    }
    
    /*静态申请设备号*/
    //chr_dev->dev_major = 77;  //静态分配设备号
    //ret = register_chrdev(dev_major, "chr_dev_test", &my_fop);
    
    /*动态申请设备号*/
    chr_dev->dev_major = register_chrdev(0, "chr_dev_test", &my_fop);
    if(chr_dev->dev_major < 0)
    {
        printk(KERN_ERR "register_chrdev error\n");
        ret = -ENODEV;
        goto err_0;
    }
  
    chr_dev->cls = class_create(THIS_MODULE, "chr_cls");
    //if(chr_dev->cls == NULL)
    /*内核中用于判断指针是否出错的API*/
    if(IS_ERR(chr_dev->cls))
    {
        prinkt(KERN_ERR "class_create error\n");
        ret = PTR_ERR(chr_dev->cls);    //将指针出错的具体原因转换为一个出错码
        goto err_1;
    }
    //chr_dev->dev = device_create(chr_dev->cls, NULL, MKDEV(chr_dev->dev_major, 0), NULL, "chr2");
    chr_dev->dev = device_create(chr_dev->cls, NULL, MKDEV(chr_dev->dev_major, 0), NULL, "chr%d", 2);
    if(IS_ERR(chr_dev->dev))
    {
        prinkt(KERN_ERR "device_create error\n");
        ret = PTR_ERR(chr_dev->dev);    //将指针出错的具体原因转换为一个出错码
        goto err_2;
    }
    /*映射物理地址*/
    chr_dev->reg_virt_base = ioremap(GPX2_CON, GPX2_SIZE);
    if(IS_ERR(chr_dev->reg_virt_base))
    {
        prinkt(KERN_ERR "ioremap error\n");
        ret = PTR_ERR(chr_dev->reg_virt_base);  //将指针出错的具体原因转换为一个出错码
        goto err_3;
    }
    u32 value = readl(chr_dev->reg_virt_base);
    value &= ~(0xf<<28);
    value |= (0x1<<28);
    writel(value, chr_dev->reg_virt_base);
    return 0;

err_3:
    device_destroy(chr_dev->cls, MKDEV(chr_dev->dev_major, 0));
err_2:
    class_destroy(chr_dev->cls);
err_1:
    unregister_chrdev(chr_dev->dev_major, "chr_dev_test");
err_0:
    kfree(chr_dev);
    return ret;
}
static void __exit chr_dev_exit(void)
{
    iounmap(chr_dev->reg_virt_base);
    device_destroy(chr_dev->cls, MKDEV(chr_dev->dev_major, 0));
    class_destroy(chr->cls);
    /*卸载设备号*/
    unregister_chrdev(chr_dev->dev_major, "chr_dev_test");
    kfree(chr_dev);
}
module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");

7.4树莓派驱动规范化

  • 改进后驱动代码
#include 
#include 
#include 
#include 
#include 
#include 
#include 

/*定义一个led驱动设备抽象类*/
struct led_desc
{
    unsigned int dev_major;
    struct class *cls;
    struct device *dev;
    /*
    基地址寄存器,此处为GPFSEL0(0x3f200000),若要配置其他寄存器地址则只需要在此基础上加偏移量即可
    如GPSET0(0x3f20001C),其地址在GPFSEL0基础上偏移了0x1C
    如GPCLR0(0x3f200028),其地址在GPFSEL0基础上偏移了0x28
    */
    void *virt_base_reg;
};
/*定义一个led设备*/
struct led_desc *led_drv;

/*寄存器物理地址宏定义*/
#define GPFSEL0 0x3f200000


/*内核空间数据*/
static int kernel_val = 555;
/*驱动实现的接口函数*/
ssize_t led_drv_read (struct file *filp, char __user *buff, size_t count, loff_t *fpos)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}
ssize_t led_drv_write (struct file *filp, const char __user *buff, size_t count, loff_t *fops)
{
    int ret;
    /*从用户空间接收数据给内核空间(kernel_val)*/
    ret = copy_from_user(&kernel_val, buff, count);
    if(ret > 0)
    {
        printk("copy from user err\n");
        return -EFAULT;
    }
    /*对用户空间接收的数据进行判断*/
    if(kernel_val){
        /*点灯*/
        writel(readl(led_drv->virt_base_reg + 0x1C) | (0x1<<4), led_drv->virt_base_reg + 0x1C);
        printk("pin4 = 1\n");
    }else{
        /*关灯*/
        writel(readl(led_drv->virt_base_reg + 0x28) | (0x1<<4), led_drv->virt_base_reg + 0x28);
        printk("pin4 = 0\n");
    }
    return 0;
}
int led_drv_open (struct inode *inode, struct file *filp)
{
    printk("gpio init...\n");
    /*配置pin4为输出*/
    u32 reg_value = readl(led_drv->virt_base_reg);
    reg_value  &= ~(0x111<<12);
    reg_value |= (0x1<<12);
    writel(reg_value, led_drv->virt_base_reg);
    printk("gpio stat:out\n");
    return 0;
}
int led_dev_close (struct inode *inode, struct file *filp)
{
    printk("------%s------\n", __FUNCTION__);
    return 0;
}


const struct file_operations my_fop = {
    .open = led_drv_open,
    .read = led_drv_read,
    .write = led_drv_write,
    .release = led_dev_close,
};
static int __init led_dev_init(void)
{
    /*保存返回值*/
    int ret;
    /*分配空间*/
    led_drv = kmalloc(sizeof(struct led_desc), GFP_KERNEL);
    if(led_drv == NULL)
    {
        /*空间分配失败*/
        printk(KERN_ERR "kmalloc error\n");
        return -ENOMEM;
    }
    led_drv->dev_major = register_chrdev(0, "led_drv", &my_fop);
    if(led_drv->dev_major < 0)
    {
        printk(KERN_ERR "register_chrdev error\n");
        ret = -ENODEV;
        goto err0;
    }
    led_drv->cls = class_create(THIS_MODULE, "led_cls");
    if(IS_ERR(led_drv->cls))
    {
        printk(KERN_ERR "class_create error\n");
        ret = PTR_ERR(led_drv->cls);
        goto err1;
    }
    led_drv->dev = device_create(led_drv->cls, NULL, MKDEV(led_drv->dev_major, 0), NULL, "led%d", 0);
    if(IS_ERR(led_drv->dev))
    {
        printk(KERN_ERR "device_create error\n");
        ret = PTR_ERR(led_drv->dev);
        goto err2;
    }
    
    /*映射物理地址*/
    led_drv->virt_base_reg = ioremap(GPFSEL0, 4);
    if(IS_ERR(led_drv->virt_base_reg))
    {
        printk(KERN_ERR "ioremap error\n");
        ret = PTR_ERR(led_drv->virt_base_reg);
        goto err3;
    }
    return 0;
    
err3:
    device_destroy(led_drv->cls, MKDEV(led_drv->dev_major, 0));
err2:
    class_destroy(led_drv->cls);
err1:
    unregister_chrdev(led_drv->dev_major, "led_drv");
err0:
    kfree(led_drv);
    return ret;
}

static void __exit led_dev_exit(void)
{
    iounmap(led_drv->virt_base_reg);
    device_destroy(led_drv->cls, MKDEV(led_drv->dev_major, 0));
    class_destroy(led_drv->cls);
    /*卸载设备号*/
    unregister_chrdev(led_drv->dev_major, "led_drv");
    kfree(led_drv);
}
module_init(led_dev_init);
module_exit(led_dev_exit);
MODULE_LICENSE("GPL");
  • 改进后测试代码
#include 
#include 
#include 
#include 
#include 
#include 

int main(int argc, char *argv[])
{
    /*调用底层对应驱动*/
    int fd;
    int value = 0;
    /*设备文件改为led0*/
    fd = open("/dev/led0", O_RDWR);
    if(fd < 0)
    {
        perror("open");
        exit(1);
    }
    /*APP控制灯*/
    while(1)
    {
        printf("pls input a cmd(0:off):");
        scanf("%d", &value);
        write(fd, &value, 4);
    }
    close(fd);
    return 0;   
}
  • 测试过程
---------------------------------------------deepin--------------------------------------------------
# 编译
hanqi@hanqi-PC:~/my_drivers$ make
make -C /home/hanqi/pi/linux-rpi-4.14.y M=/home/hanqi/my_drivers modules ARCH=arm CROSS_COMPILE=/home/hanqi/pi/toolchain/tools-master/arm-bcm2708/arm-linux-gnueabihf/bin/arm-linux-gnueabihf-
make[1]: Entering directory '/home/hanqi/pi/linux-rpi-4.14.y'
  CC [M]  /home/hanqi/my_drivers/led_drv.o
/home/hanqi/my_drivers/led_drv.c: In function 'led_drv_open':
/home/hanqi/my_drivers/led_drv.c:59:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
     u32 reg_value = readl(led_drv->virt_base_reg);
     ^
  Building modules, stage 2.
  MODPOST 1 modules
  CC      /home/hanqi/my_drivers/led_drv.mod.o
  LD [M]  /home/hanqi/my_drivers/led_drv.ko
make[1]: Leaving directory '/home/hanqi/pi/linux-rpi-4.14.y'
/home/hanqi/pi/toolchain/tools-master/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin/arm-linux-gnueabihf-gcc led_drv_test.c -o led_drv_test
# 拷贝ko文件到指定目录
hanqi@hanqi-PC:~/my_drivers$ make install 
cp -raf *.ko led_drv_test /home/hanqi/my_drivers_ko
# 将ko文件发送给树莓派
hanqi@hanqi-PC:~/my_drivers$ scp led_drv.ko [email protected]:/home/pi/my_drivers
# 将测试文件发送给树莓派
hanqi@hanqi-PC:~/my_drivers$ scp led_drv_test [email protected]:/home/pi/my_drivers

--------------------------------------------pi-------------------------------------------------------
# 安装驱动
pi@raspberrypi:~/my_drivers $ sudo insmod led_drv.ko
# 查看是否生成设备文件,此处已经生成
pi@raspberrypi:~/my_drivers $ ls /dev/led0           
/dev/led0
# 查看生成的设备文件详细信息,其中主设备号为243,次设备号为0
pi@raspberrypi:~/my_drivers $ ls /dev/led0 -l
crw------- 1 root root 243, 0 Oct  5 14:59 /dev/led0
# 执行测试文件
pi@raspberrypi:~/my_drivers $ sudo ./led_drv_test 
pls input a cmd(0:off):0
pls input a cmd(0:off):1
pls input a cmd(0:off):0
pls input a cmd(0:off):^C
# 查看内核调试信息
pi@raspberrypi:~ $ dmesg 
[   46.316442] led_drv: loading out-of-tree module taints kernel.
[   70.252405] gpio init...
[   70.252416] gpio stat:out
# pi电压不足,emmmmmmmmmm
[  128.946989] Under-voltage detected! (0x00050005)
[  135.186943] Voltage normalised (0x00000000)
[  137.907128] pin4 = 0
[  138.831221] pin4 = 1
[  140.226740] pin4 = 0
[  141.793453] ------led_dev_close------
# 查看引脚信息,由于效果跟上次实验一样,此处省略几百字,参见6.3.1.2
pi@raspberrypi:~/my_drivers $ gpio readall
# 卸载驱动
pi@raspberrypi:~/my_drivers $ sudu rmmod led_drv.ko

8.总结:编写字符设备驱动步骤

1.实现模块加载与卸载函数

module_init(chr_dev_init);

module_exit(chr_dev_exit);

2.在模块加载函数中

申请主设备号(用于区分与管理不同的字符设备)、

int ret = register_chrdev(dev_major, "chr_dev_test", &my_fop);

创建设备节点文件(为用户提供一个可操作文件接口)、

struct class *devcls = class_create(THIS_MODULE, "chr_cls");

struct device *dev = device_create(devcls, NULL, MKDEV(dev_major, 0), NULL, "chr2");

硬件初始化(地址映射,中断申请,硬件寄存器初始化)、

gpx2conf = ioremap(GPX2_CON, GPX2_SIZE);
gpx2data = gpx2conf + 1;
*gpx2conf &= ~(0xf<<28);
*gpx2conf |= (0x1<<28);

实现接口

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

你可能感兴趣的:(T4 Linux字符设备驱动开发)