T4 Linux字符設備驅動開發(fā)

1.驅動分類

1.1字符設備

1.1.1特點

  • 數據為字符流,數據從寄存器產生
  • 傳輸數據少而快
  • 如LCD屏,keyboard,IIC等

1.1.2上層調用方式

  • 上層應用以文件描述符形式打開驅動,如open,read,write
  • 上層每調用某函數,在驅動層面都有相應接口函數,如上層open對于底層xxx_open
  • 為方便上層應用程序尋找底層驅動,區(qū)分驅動類型,因此引入設備號的概念.類似于身份證號碼
  • 驅動會對外提供自己的名稱與設備號,名稱在一個系統里面不唯一,但是設備號卻是唯一的
  • 例如某led驅動,其名稱為/dev/leds(名稱叫設備節(jié)點),還有設備號
  • 即linux將一切設備當成文件,假如上層通過open打開某led驅動,那么對于底層驅動就會有對應led_open函數來操作硬件

1.2塊設備

1.2.1特點

  • 數據以塊為單位
  • 對于一些存儲設備,例如flash,磁盤
  • 傳輸數據大而慢

1.3網絡設備

1.3.1特點

  • 涉及協議等,同一由socket管理

2.申請設備號

2.1概念

  • 設備號類似身份證,用于區(qū)分驅動
  • 其本質就是一個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 <linux/fs.h>
/*
功能:申請設備號
參數:
參數1:主設備號,(0表示由系統動態(tài)分配;也可以靜態(tài)指定一個整數,注意不要與已經存在設備號沖突)
參數2:描述設備信息,在/proc/devices目錄下列出所有已經注冊的設備
參數3:文件操作對象,提供open,read,write等
返回值:正確返回0,失敗返回負數
*/

int register_chrdev(unsigned int major, const char * name, const struct file_operations * fops);
    
/*
功能:釋放設備號
參數:
參數1:主設備號,(0表示由系統動態(tài)分配;也可以靜態(tài)指定一個整數,注意不要與已經存在設備號沖突)
參數2:描述設備信息,在/proc/devices目錄下列出所有已經注冊的設備
返回值:無
*/
    
void unregister_chrdev(unsigned int major, const char * name);

2.3示例代碼

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

/*靜態(tài)指定主設備號*/
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.創(chuàng)建設備節(jié)點

3.1創(chuàng)建方式

3.1.1手動創(chuàng)建

  • 手動創(chuàng)建mknode /dev/設備名 類型 主設備號 次設備號,例如mknode /dev/chr0 c 77 0
# 創(chuàng)建設備節(jié)點
pi@raspberrypi:~/my_drivers $ sudo mknod /dev/chr0 c 77 0
# 查看是否創(chuàng)建成功
pi@raspberrypi:~/my_drivers $ ls /dev/chr0 -l            
crw-r--r-- 1 root root 77, 0 Aug 30 10:55 /dev/chr0
  • 手動創(chuàng)建缺點:/dev/xx目錄文件存在于內存里面,斷電之后所創(chuàng)建的節(jié)點將會消失

3.1.2自動創(chuàng)建(通過udev/mdev機制)

  • API
#include <linux/device.h>

/*
功能:創(chuàng)建一個類
參數:
參數1:一般寫為THIS_MODULE
參數2:字符串名字,自己寫
返回值:class結構體類型指針
*/
struct class *class_create(owner, name);

/*
功能:銷毀一個創(chuàng)建的類
參數:class_create創(chuàng)建的class結構體類型指針
*/
class_destroy(struct class * cls);
-----------------------------------------------------------------------------------------------------
/*
功能:創(chuàng)建一個設備文件
參數:
參數1:class_create創(chuàng)建的class結構體類型指針
參數2:父親,一般直接寫NULL
參數3:設備號,通過MDKEV()宏創(chuàng)建
參數4:私有數據,一般寫NULL
參數5,6:可變參數,一般為字符串,表示設備節(jié)點名字
返回值:device類型指針
*/
struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt, ...);

/*
功能:銷毀一個創(chuàng)建的設備文件
參數:
參數1:class_create創(chuàng)建的class結構體類型指針
參數2:設備號,通過MDKEV()宏創(chuàng)建
*/
device_destroy(struct class * class, dev_t devt);
-----------------------------------------------------------------------------------------------------
/*
功能:通過主設備號與次設備號合成一個設備號
參數:
參數1:主設備號
參數2:次設備號
返回值:設備號
*/
#define MKDEV(ma, mi) ((ma) << 20 | (mi))

3.1.3自動創(chuàng)建示例代碼

  • 代碼,注意:入口函數里面的申請資源的函數順序與釋放資源函數里面的函數執(zhí)行順序相反
  • 例如,初始化順序為register_chrdev,class_create,device_create;釋放資源順序為device_destroy,class_destroy,unregister_chrdev
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>

/*靜態(tài)指定主設備號*/
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下創(chuàng)建了一個chr2的設備節(jié)點,卸載后節(jié)點消失
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 <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>

/*靜態(tài)指定主設備號*/
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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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,使用開發(fā)板的交叉編譯工具鏈編譯測試代碼
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

  • 執(zhí)行結果,可見上層調用open對應在驅動里面調用chr_drv_open
# 裝載驅動
pi@raspberrypi:~/my_drivers $ sudo insmod chr_drv.ko
# 查看設備節(jié)點是否創(chuàng)建成功
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 <linux/uaccess.h>
#include <asm/uaccess.h>
/*
功能:將數據從內核空間拷貝到用戶空間
參數:
參數1:應用空間的緩沖區(qū)
參數2:內核驅動中的緩沖區(qū)
參數3:數據大小
返回值:大于0表示錯誤(即未被拷貝的數據),等于0表示正確(沒剩余,拷貝成功)
*/

int copy_to_user(void __user *to, const void *from, unsigned long n);
  • copy_from_user函數,一般用于驅動內部write接口函數
#include <linux/uaccess.h>
#include <asm/uaccess.h>
/*
功能:從用戶空間拷貝數據到內核空間
參數:
參數1:內核驅動中的緩沖區(qū)
參數2:應用空間的緩沖區(qū)
參數3:數據大小
返回值:大于0表示錯誤(即未被拷貝的數據),等于0表示正確(沒剩余,拷貝成功)
*/

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

5.3代碼示例

  • 內核驅動代碼
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>


/*靜態(tài)指定主設備號*/
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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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;   
}
  • 執(zhí)行結果,可看到用戶接收到內核的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 <asm/io.h>
/*
功能:將物理地址映射為虛擬地址
參數:
參數1:要映射的起始的IO地址
參數2:要映射的空間的大小
返回值:虛擬地址
*/

void *ioremap(cookie, size)
  • iounmap函數
#include <asm/io.h>
/*
功能:解除地址映射關系
參數:映射之后的虛擬地址
返回值:無
*/

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]四位進行配置,將其配置為輸出狀態(tài);針對數據寄存器,對其第7位操作即可
  • 驅動代碼
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>

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

/*存放虛擬地址的指針,4Byte*/
volatile unsigned long *gpx2conf;
volatile unsigned long *gpx2data;

/*靜態(tài)指定主設備號*/
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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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 <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>


/*寄存器物理地址宏定義*/
#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;

/*靜態(tài)指定主設備號*/
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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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引腳狀態(tài)
# 可看到BCM 4引腳為輸出狀態(tài)為高電平
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引腳為輸出狀態(tài)為低電平
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引腳為輸出狀態(tài)為高電平
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.規(guī)范化編寫驅動

7.1規(guī)范

  • 按照上述的方式雖然將功能實現出來吱肌,但是其編程方式并不規(guī)范瓜喇。
  • 實際上編寫驅動程序需要使用到面向對象的編程思想蹦骑。在C語言中使用結構體表示一個對象,這樣可以提升代碼可移植性和可閱讀性
  • 還需要實現對應的出錯機制

及時釋放分配的內存資源火架,防止內存泄漏(通過goto實現)

7.2規(guī)范化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驅動規(guī)范化

  • 內核內存分配API
#include <linux/slab.h>

/*
功能:內核中來為某變量分配空間
參數:
參數1:要分配的塊的大小
參數2:分配標志
    GFP_KERNEL:內核內存的通常分配方法,可能會引起休眠。即如果當前內存不夠用的時候幼东,該函數會一直阻塞
    GFP_USER:用于為用戶空間分配內存,可能會休眠檀夹。
    GFP_ATOMIC:用于在中斷處理例程或其他運行于進程上下文之外的代碼中分配內存筋粗,不會休眠策橘。
    GFP_HIGHUSER:用于為用戶空間分配內存炸渡,指高端內存分配,可能會休眠丽已。
    GFP_NOIO:禁止任何I/O的初始化(主要在虛擬內存代碼中使用)蚌堵。
    GFP_NOFS:分配不允許執(zhí)行任何文件系統調用(主要在文件系統代碼中使用)。
****************************************************************
分割線以上的flag可以和分割線以下的flag “或”起來使用
****************************************************************
    __GFP_DMA:該標志請求分配發(fā)生在可進行DMA的內存區(qū)段中沛婴。
    __GFP_HIGHMEM:該標志表明要分配的內存可位于高端內存吼畏。
    __GFP_NOWAPN:該標志使用的次數較少,它主要是避免內核在無法滿足分配請求時產生警告信息嘁灯。
    __GFP_COLD:該標志表示請求尚未使用的“冷”頁面泻蚊。
    __GFP_HIGH:該標記標記了一個高優(yōu)先級的請求,它允許為緊急狀況而消耗由內核保留的最后一些頁面丑婿。
    __GFP_REPEAT:該標志表示在分配器在滿足分配請求而遇到困難時性雄,“努力再嘗試一次”,它會重新嘗試分配羹奉,但還是有失敗的可能性秒旋。
    __GFP_NOFAIL:該標志表示在分配器在滿足分配請求而遇到困難時告訴分配器始終不返回失敗。
    __GFP_NORETRY:該標志表示再請求內存不可獲得的時候會立即返回诀拭。
    
    GFP_前綴是由于在分配內存時總是調用get_free_page來實現實際的分配而得來的縮寫迁筛。
返回值:分配內存的首地址
注意:用戶空間malloc是基于堆內存分配,內核負責管理系統物理內存耕挨,物理內存只能按頁面進行分配细卧,因此,kmalloc是基于頁進行分配筒占。另外需要注意的一點是內核只能分配一些預定義的贪庙、固定大小的字節(jié)數組。kmalloc 可以處理的最小的內存塊是32或64赋铝,最大分配的內存大小為128K插勤。
*/
void *kmalloc(size_t size, int flags);
  • 操作地址API
#include <asm/io.h> 
/*
功能:向某個地址中寫入值
參數:
參數1:要寫入的數據
參數2:寫入數據的地址
返回值:無
*/

void writel (unsigned char data , unsigned short addr )

/*
功能:從某個地址中讀取地址空間的值
參數:
參數1:要讀取的地址
返回值:讀取的地址,u32類型
*/
unsigned char readl (unsigned int addr )
  • 面向對象思想,在C中使用結構體來表現一個對象
  • 出錯處理
  • 改進后
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>


/*設計一個類农尖,用來描述設備信息*/
struct chr_desc
{
    unsigned int dev_major;         //描述設備號
    struct class *cls;
    struct device *dev;             //創(chuàng)建設備文件
    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;
    }
    
    /*靜態(tài)申請設備號*/
    //chr_dev->dev_major = 77;  //靜態(tài)分配設備號
    //ret = register_chrdev(dev_major, "chr_dev_test", &my_fop);
    
    /*動態(tài)申請設備號*/
    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樹莓派驅動規(guī)范化

  • 改進后驅動代碼
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <asm/io.h>
#include <linux/slab.h>

/*定義一個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 <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

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文件發(fā)送給樹莓派
hanqi@hanqi-PC:~/my_drivers$ scp led_drv.ko pi@172.18.26.188:/home/pi/my_drivers
# 將測試文件發(fā)送給樹莓派
hanqi@hanqi-PC:~/my_drivers$ scp led_drv_test pi@172.18.26.188:/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
# 執(zhí)行測試文件
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.在模塊加載函數中

申請主設備號(用于區(qū)分與管理不同的字符設備)膝昆、

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

創(chuàng)建設備節(jié)點文件(為用戶提供一個可操作文件接口)、

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,
};

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末纬朝,一起剝皮案震驚了整個濱河市收叶,隨后出現的幾起案子,更是在濱河造成了極大的恐慌共苛,老刑警劉巖判没,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異俄讹,居然都是意外死亡哆致,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進店門患膛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來摊阀,“玉大人,你說我怎么就攤上這事踪蹬“耍” “怎么了?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵跃捣,是天一觀的道長漱牵。 經常有香客問我,道長疚漆,這世上最難降的妖魔是什么酣胀? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任刁赦,我火速辦了婚禮,結果婚禮上闻镶,老公的妹妹穿的比我還像新娘甚脉。我一直安慰自己,他們只是感情好铆农,可當我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布牺氨。 她就那樣靜靜地躺著,像睡著了一般墩剖。 火紅的嫁衣襯著肌膚如雪猴凹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天岭皂,我揣著相機與錄音郊霎,去河邊找鬼。 笑死蒲障,一個胖子當著我的面吹牛歹篓,可吹牛的內容都是我干的瘫证。 我是一名探鬼主播揉阎,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼背捌!你這毒婦竟也來了毙籽?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤毡庆,失蹤者是張志新(化名)和其女友劉穎坑赡,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體么抗,經...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡毅否,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年唧瘾,在試婚紗的時候發(fā)現自己被綠了棕硫。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡景东,死狀恐怖吞琐,靈堂內的尸體忽然破棺而出捆探,到底是詐尸還是另有隱情,我是刑警寧澤站粟,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布黍图,位于F島的核電站,受9級特大地震影響奴烙,放射性物質發(fā)生泄漏助被。R本人自食惡果不足惜剖张,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望揩环。 院中可真熱鬧修械,春花似錦、人聲如沸检盼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吨枉。三九已至蹦渣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間貌亭,已是汗流浹背柬唯。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留圃庭,地道東北人锄奢。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像剧腻,于是被迫代替她去往敵國和親拘央。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內容