一 、关于教程:
高于2.6版kernel对应的驱动编程可参考文档: https://tldp.org/LDP/lkmpg/2.6/lkmpg.pdf,2.4版的已经旧了。有一份中文版(参杂了2.4和2.6)可以供英文不熟的同学参考:http://www.embeddedlinux.org.cn/linuxmod/ 。
想让编译过程输出log,可以修改 /lib/modules/$(shell uname −r)/build 目录下的Makefile,将其中变量quiet和Q的内容注释掉,下面的两个#是加上让输出log的。
ifeq ($(KBUILD_VERBOSE),1)
quiet =
Q =
else
quiet= #quiet_
Q = #@
endif
在学习中,试着编写一个包含多个源文件的ko,碰到了一些问题。总结如下:
obj-m 指明要编译出来的ko的名字,但后缀名是.o ,它不要和实际c文件的名字一样。比如起一个helloworldMod1,那就不能有helloworldMod1.c 的源文件。写成下面这行:
obj-m := helloworldMod1.o
然后用ko的名字加上 -objs 指明它依靠的obj文件:
helloworldMod1-objs := helloworldmod.o mathhy.o
helloworldmod.o mathhy.o 分别是 helloworldmod.c mathhy.c 对应的目标文件。这样就可以产生正确的ko。
helloworldMod.c 代码如下:
#include <linux/module.h>
#include <linux/init.h>
MODULE_LICENSE("Dual BSD/GPL");
extern int addfg(int, int);
static int hello_init(void)
{
int c = addfg(1,-34);
if( c < 0 )
printk(KERN_ALERT"HELLO WORLD\n");
printk( KERN_ALERT"Hello World\n") ;
return 0 ;
}
static void hello_exit(void)
{
printk(KERN_ALERT"Hello World exit\n");
return ;
}
module_init(hello_init) ;
module_exit(hello_exit);
mathhy代码如下:
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
int addfg( int a , int b )
{
return a+b;
}
Makefile 代码如下:
obj-m:=helloworldMod1.o
helloworldMod1-objs:=mathhy.o helloworldMod.o
KDIR=/lib/modules/5.4.0-53-generic/build
all:
make -C $(KDIR) M=$(shell pwd) modules
二、参考 https://blog.csdn.net/JeromeCoco/article/details/108054698写一个驱动的过程,支持创建多个驱动文件
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define DEV_NAME "jeromeCharDev"
#define DEV_CNT (3) //May create 3 device files ,minor device is 0,1,2
#define BUFF_SIZE 128
static dev_t devno;
static struct cdev chr_dev;
static char vbuf[BUFF_SIZE];
static int chr_dev_open(struct inode *inode, struct file *filp);
static int chr_dev_release(struct inode *inode, struct file *filp);
static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos);
static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos);
static struct file_operations chr_dev_fops =
{
.owner = THIS_MODULE,
.open = chr_dev_open,
.release = chr_dev_release,
.write = chr_dev_write,
.read = chr_dev_read,
};
static int chr_dev_open(struct inode *inode, struct file *fil
{
int minor = iminor( inode ) ; //get minor device No
printk("\nopen dev %d\n" , minor );
return 0;
}
static int chr_dev_release(struct inode *inode, struct file *filp)
{
printk("\nrelease\n");
return 0;
}
static ssize_t chr_dev_write(struct file *filp, const char __user * buf, size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
int ret;
int tmp = count ;
int minor = iminor( filp->f_inode ) ;//get minor device No
printk("\nwrite dev %d\n" , minor );
if(p > BUFF_SIZE)
return 0;
if(tmp > BUFF_SIZE - p)
tmp = BUFF_SIZE - p;
ret = copy_from_user(vbuf, buf, tmp);
*ppos += tmp;
return tmp;
}
static ssize_t chr_dev_read(struct file *filp, char __user * buf, size_t count, loff_t *ppos)
{
int minor ;
unsigned long p = *ppos;
int ret;
int tmp = count ;
static int i = 0;
i++;
minor = iminor( filp->f_inode ) ; //get minor device No
printk("\nread dev %d\n" , minor );
if(p >= BUFF_SIZE)
return 0;
if(tmp > BUFF_SIZE - p)
tmp = BUFF_SIZE - p;
ret = copy_to_user(buf, vbuf+p, tmp);
*ppos +=tmp;
return tmp;
}
static int __init chrdev_init(void)
{
int ret = 0;
printk("chrdev init\n");
ret = alloc_chrdev_region(&devno, 0, DEV_CNT, DEV_NAME);
if(ret < 0){
printk("fail to alloc devno\n");
goto alloc_err;
}
cdev_init(&chr_dev, &chr_dev_fops);
ret = cdev_add(&chr_dev, devno, DEV_CNT);
if(ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
return 0;
add_err:
unregister_chrdev_region(devno, DEV_CNT);
alloc_err:
return ret;
}
module_init(chrdev_init);
static void __exit chrdev_exit(void)
{
printk("chrdev exit\n");
unregister_chrdev_region(devno, DEV_CNT);
cdev_del(&chr_dev);
}
module_exit(chrdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiechen");
Makefile:
obj-m := jeromeDev.o
KERNELBUILD :=/lib/modules/$(shell uname -r)/build
default:
make -C $(KERNELBUILD) M=$(shell pwd) modules
clean:
rm -rf *.o *.ko *.mod.c .*.cmd *.markers *.order *.symvers .tmp_versions
创建设备文件,向设备文件输出和读入的过程:
vic@vic-VirtualBox:~/module-try/chardrv2$ sudo insmod jeromeDev.ko
vic@vic-VirtualBox:~/module-try/chardrv2$ lsmod |grep jeromeDev
jeromeDev 12738 0
vic@vic-VirtualBox:~/module-try/chardrv2$ cat /proc/devices
Character devices:
1 mem
...
216 rfcomm
249 jeromeCharDev
vic@vic-VirtualBox:~/module-try/chardrv2$ dmesg -T
[一 1月 11 14:26:14 2021] chrdev exit
[一 1月 11 14:28:34 2021] chrdev init
创建设备文件:
vic@vic-VirtualBox:~/module-try/chardrv2$ sudo mknod /dev/hh0 c 249 0
vic@vic-VirtualBox:~/module-try/chardrv2$ sudo mknod /dev/hh1 c 249 1
往设备文件输出:
vic@vic-VirtualBox:~/module-try/chardrv2$ echo 12345=>/dev/hh0 > /dev/hh0
bash: /dev/hh0: 拒絕不符權限的操作
vic@vic-VirtualBox:~/module-try/chardrv2$ ls -l /dev/hh?
crw-r--r-- 1 root root 249, 0 1月 11 14:33 /dev/hh0
crw-r--r-- 1 root root 249, 1 1月 11 14:33 /dev/hh1
修改权限:
vic@vic-VirtualBox:~/module-try/chardrv2$ sudo chmod 777 /dev/hh?
vic@vic-VirtualBox:~/module-try/chardrv2$ ls -l /dev/hh?
crwxrwxrwx 1 root root 249, 0 1月 11 14:33 /dev/hh0
crwxrwxrwx 1 root root 249, 1 1月 11 14:33 /dev/hh1
可以读写了:
vic@vic-VirtualBox:~/module-try/chardrv2$ echo 12345=>/dev/hh0 > /dev/hh0
vic@vic-VirtualBox:~/module-try/chardrv2$ cat /dev/hh0
12345=
vic@vic-VirtualBox:~/module-try/chardrv2$ echo "12345=>/dev/hh0" > /dev/hh0
vic@vic-VirtualBox:~/module-try/chardrv2$ cat /dev/hh0
12345=>/dev/hh0
vic@vic-VirtualBox:~/module-try/chardrv2$ echo "abcde=>/dev/hh1" > /dev/hh1
vic@vic-VirtualBox:~/module-try/chardrv2$ cat /dev/hh1
abcde=>/dev/hh1
vic@vic-VirtualBox:~/module-try/chardrv2$
由于在jeromeDev.c 里指定DEV_CNT为3 //May create 3 device files ,minor device is 0,1,2,所以读写/dev/hh4(此设备号为4)失败:
vic@vic-VirtualBox:~/module-try/chardrv2$ sudo mknod /dev/hh4 c 249 4
vic@vic-VirtualBox:~/module-try/chardrv2$ sudo chmod 777 /dev/hh4
vic@vic-VirtualBox:~/module-try/chardrv2$ echo 123 > /dev/hh4
bash: /dev/hh4: 沒有此一裝置或位址
log:
vic@vic-VirtualBox:~/module-try/chardrv2$ dmesg -T
[一 1月 11 14:38:24 2021] open dev 0
[一 1月 11 14:38:24 2021] read dev 0
[一 1月 11 14:38:24 2021] read dev 0
[一 1月 11 14:38:24 2021] release
[一 1月 11 14:39:27 2021] open dev 1
[一 1月 11 14:39:27 2021] write dev 1
[一 1月 11 14:39:27 2021] release
[一 1月 11 14:39:32 2021] open dev 1
[一 1月 11 14:39:32 2021] read dev 1
[一 1月 11 14:39:32 2021] read dev 1
[一 1月 11 14:39:32 2021] release
三、使用ioremap根据datasheet里寄存器地址获取内核空间的虚拟地址,从而访问GPIO寄存器
参考:https://blog.csdn.net/qq_37596943/article/details/103811120
在Linux系统中,不管是在用户空间还是内核空间一律不允许直接访问硬件外设的基地址(包括寄存器的基地址)。
如果要想访问,必须将外设的基地址映射到用户空间的虚拟地址或者内核空间的虚拟地址。
用户虚拟地址范围:0x00000000~0xBFFFFFFF
内核虚拟地址范围:0xC0000000~0xFFFFFFFF
void *ioremap(unsigned long phys_addr, int size)
- 函数功能:完成物理地址和内核虚拟地址的映射关系。
- 参数:
- phys_addr:要访问、映射的外设的物理地址起始地址。
- size:要访问、映射的外设的物理地址范围(大小)
- 返回值:返回映射的内存起始虚拟地址。
ioremap 只能映射一次,重复映射会失败。
查看已经使用物理地址:cat /proc/iomem
raspberryPi 的iomem状况:
上图,iomem文件的第一栏是物理内存,右边指明使用该块的设备
四、led驱动参考:https://blog.csdn.net/qq_22966507/article/details/109636389
raspberryPi 4B GPIO 标号图示: