Linux一切皆文件
fd = open ("/dev/pin4",权限);
怎么才能找到驱动?
1.文件名
2.设备号
驱动代码有一个驱动链表:
管理所有设备驱动
1.添加
2.查找
编写完驱动代码,加载到内核
调用驱动程序,用户空间去open
开发:驱动插入链表的顺序有设备号检索
写代码
1.驱动代码基本框架
随便一个名字:pin4Drivers.c
主要的框架是这个函数:pin4_drv_init
其他基本是围绕它的变量写的一些定义,内容的补充
#include <linux/fs.h> //file_operation声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //_init _exit 宏定义声明
#include <linux/device.h> //class device 声明
#include <linux/uaccess.h> //copy_from_user 的头声明
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap 的头文件
//变量的定义
static struct class *pin4_calss;
static struct device *pin4_dev;
static dev_t devno; //设备号
static int major = 231; //主设备号
static int minor = 0; //次设备号
static char *module_name = "pin4"; //模块名
//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //printk:内核的打印函数,跟printf类似
return 0;
}
//pin4_write 函数
static ssize_t pin4_write(struct file *file)//上层调用write,这里就调用pin4write
{
printk("pin4_write\n");
return 0;
}
static struct file_operations pin4_fops ={
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
/*
struct abc{
int a;
int b;
int c;
};
struct abc ab = {
.a = 1;
.c = 2; //直接给c赋值,如果没有.a,.c就按顺序赋值
};
*/
int _init pin4_drv_init(void) //真实驱动入库
{
int ret;
devno = MKDEV(major,minor); //创建设备号:参数是主设备号和次设备号
ret = register_chrdev(major,module_neme,&pin4_fops); //注册驱动告诉内核,把这个驱动加入到内核,参数分别是:主设备号,模组名"pin4",操作函数结构体
pin4_calss = class_create(THIS_MODULE,"myfirstdemo"); //让代码在/dev下自动生成设备
//也可以手动生成sudo mknod abc 8 1 //主动生成设备,主设备号是8,次设备号是1,名字是abc
pin4_calss_dev = device_create(pin4_calss,NULL,devno,NULL,module_neme); //创建设备文件
return 0;
}
void _exit pin4_drv_exit(void)
{
device_destroy(pin4_calss,devno);//销毁设备
class_destroy(pin4_calss);//销毁类
unregister_chrdev(major,module_neme); //卸载驱动
}
module_init(pin4_drv_init); //入口,内核加载该驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
2.把代码移动到 /linux-rpi-4.14.y/drivers/char$
驱动设备分为字符设备,块设备,网络设备驱动
不是非得放在char文件夹,无论放在哪个文件夹,都要修改那个文件夹的Makefile
/linux-rpi-4.14.y不知道是什么可以看这个博客
3.配置:Makefile 让工程编译的时候可以编译pin4Drivers.c
驱动可以编译成模块,也可以编译进内核,这里ogj-m是编译成模块
obj-m += pin4Drivers.o
然后回到上两层目录
hgs@ubuntu:~/raspberry/linux-rpi-4.14.y/drivers/char$ cd ../..
hgs@ubuntu:~/raspberry/linux-rpi-4.14.y$
编译生成驱动
这个命令用来编译内核的,类似gcc,不过是比gcc长很多
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make
arm:arm架构
CROSS_COMPILE :交叉编译的意思
arm-linux-gnueabihf-:arm-linux-gnueabihf-gcc就是可以编译出树莓派的可执行文件的编译器,详情可查看交叉编译树莓派
Kernel 操作系统内核 操作系统内核是指大多数操作系统的核心部分。它由操作系统中用于管理存储器、文件、外设和系统资源的那些部分组成。操作系统内核通常运行进程,并提供进程间的通信。
下面列出了它的一些核心功能:事件的调度和同步。进程间的通信(消息传递)。存储器管理。进程管理。
KERNEL=kernel7 :操作系统内核版
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 make -j4 modules
编译的结果是生成一个:pin4Drivers.ko文件
1.拷贝到树莓派
hgs@ubuntu:~/raspberry/linux-rpi-4.14.y$ scp drivers/char/pin4Drivers.ko pi@192.168.101.81:/home/pi
pi@192.168.101.81's password:
pin4Drivers.ko 100% 5972 545.3KB/s 00:00
2.树莓派加载驱动到内核的驱动链表
pi@raspberrypi:~ $ sudo insmod pin4Drivers.ko
lsmod列出内核的驱动单元
查看/dev下是否生成了pin4
卸载驱动:因为加载后文件名是pin4Drivers,sudo rmmod pin4Drivers
pi@raspberrypi:~ $ ls /dev/pin4
/dev/pin4
查看设备号是不是我们设定的
pi@raspberrypi:~ $ ls /dev/pin4 -l
crw------- 1 root root 231, 0 Apr 11 04:05 /dev/pin4
虚拟文件系统会根据我们的pin4(驱动名字)的这备号mgjor,minor在驱动链表找到我们的驱动

3.交叉编译测试程序
*也可以在树莓派写和编译,只是在64位host上编写和编译比在arm更顺畅
a.把程序拷贝到,Ubuntu
hgs@ubuntu:~$ cp /mnt/hgfs/share/pin4test.c .
b.交叉编译
arm-linux-gnueabihf-gcc pin4test.c -o pin4test
c.拷贝到树莓派
scp pin4test pi@192.168.101.81:/home/pi
d.运行
pi@raspberrypi:~ $ ls /dev/pin4 -l
crw------- 1 root root 231, 0 Apr 11 06:26 /dev/pin4
要用超级用户权限运行
或者修改pin4的权限
sudo chmod 666 /dev/pin4
666:让所有人都可以修改的权限
pi@raspberrypi:~ $ sudo chmod 600 /dev/pin4 :改成超级用户权限才能RW
查看内核态打印的数据:dmesg
pi@raspberrypi:~ $ ./pin4test
open filed
reson:: Permission denied
pi@raspberrypi:~ $ sudo ./pin4test
open succeed
pi@raspberrypi:~ $ sudo chmod 666 /dev/pin4
pi@raspberrypi:~ $ ./pin4test
open succeed
io操控代码编程
地址
1.总线地址
百度百科:地址总线 (Address Bus;又称:位址总线) 属于一种电脑总线 (一部份),是由CPU 或有DMA 能力的单元,用来沟通这些单元想要存取(读取/写入)电脑内存元件/地方的实体位址。
实际上是cpu能够访问内存的范围
现象:装了32位的win7,明明有8G内存条,系统只识别了3.8G,只有装了64位系统,才能识别8G
32位能表示/访问: 4,294,967,296 bit,
系统只识别了3.8G还剩0.2G哪去了,用作其他用途,就像买4G sd卡,插到电脑看到也不够4G
| Gbit | Mbit | Kbit | bit |
|---|---|---|---|
| 4 | 4,096 | 4,194,304 | 4,294,967,296 |
2.物理地址
硬件实际地址或绝对地址
硬盘上排列的地址
3.虚拟地址
百度百科:虚拟地址是Windows程序时运行在386保护模式下,这样程序访问存储器所使用的逻辑地址称为虚拟地址,与实地址模式下的分段地址类似,虚拟地址也可以写为“段:偏移量”的形式,这里的段是指段选择器。
逻辑(基于算法的地址(软件层面,假))地址
树莓派BCM2835芯片手册:
cpu32位 FFFFFFFF 4,294,967,295 (4G):因为从零开始算所以到4,294,967,295
RAM 4000000 (1G)
虚拟地址 FFFFFFFF (4G)

当要运行的程序大小超过一个G,是不是跑不了?
不是:ARM MMU会把物理地址ARM Physical Addresses映射成虚拟地址:ARM Virtual Addresses
上层应用程序和底层内核操作的都是虚拟地址,它有一个页表(把1M物理地址扩充成4M虚拟地址的算法),设计好页表通过MMU执行,页表决定了这1M在10~14M还是哪里表示成4M,
32为什么跑不了Linux:因为32没有MMU单元,页表的算法支持不了,
就有人对内核进行阉割,去掉MMU的单元,修改内核的计算法则变成阉割版的Linux的系统
查看树莓派芯片手册
五个寄存器
每个寄存器分别有9个io口

第六章:驱动
树莓派的io控件地址是0x3f000000加上gpio的偏移量是0x200000,所以GPIO的物理地址是0x3f200000开始,然后在这个基础上,进行Linux系统的MMU虚拟化管理,映射到虚拟地址上,
特别注意:BCM2708和BCM2709 的IO起始地址不同,BNC2708是0x20000000,BCM2709是0x3f000000,这是造成大部分人驱动出现"段错误"的原因.树莓派3B的cpu为BCM2709.
如果有电路图:通过电路图找到寄存器
GPFSEL0 GPIO Function Select 0 功能选择input/output(GPIO Function Select Registers (GPFSELn))
GPFSEL0"选择寄存器0"

pin4 14-12

000 = GPIO Pin 9 is an input
001 = GPIO Pin 9 is an output
GPSET1 GPIO Pin Output Set 1 输出1

0 = No effect
1 = Set GPIO pin n
GPCLR0 GPIO Pin Output Clear 0 清零

0 = No effect
1 = Clear GPIO pin n
volatile的作用是作为指令关键字,确保本条指令不会因编译器的优化而省略
改写代码:
#include <linux/fs.h> //file_operation声明
#include <linux/module.h> //module_init module_exit声明
#include <linux/init.h> //_init _exit 宏定义声明
#include <linux/device.h> //class device 声明
#include <linux/uaccess.h> //copy_from_user 的头声明
#include <linux/types.h> //设备号 dev_t 类型声明
#include <asm/io.h> //ioremap iounmap 的头文件
//变量的定义
static struct class *pin4_calss;
static struct device *pin4_calss_dev;
static dev_t devno; //设备号
static int major = 231; //主设备号
static int minor = 0; //次设备号
static char *module_name = "pin4"; //模块名
//选择寄存器
volatile unsigned int* GPFSEL0 = NULL;//选择第0个寄存器
volatile unsigned int* GPSET0 = NULL;//set0寄存器
volatile unsigned int* GPCLR0 = NULL;//清0寄存器
//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{
printk("pin4_open\n"); //printk:内核的打印函数,跟printf类似
//这里是调用已经存在链表的时候GPFSEL0
*GPFSEL0 &= ~(0x7 << 12);//把12到14位清出来,变成000
*GPFSEL0 |= (0x1 << 12);//把12到14位写成001
return 0;
}
//pin4_write 函数
static ssize_t pin4_write(struct file *file,const char __user *buf,ssize_t count,loff_t *pin4_fops)//上层调用write,这里就调用pin4write
{
int userCmd;
printk("pin4_write\n");
//获取上层write函数的值
//跟这个函数对应fd = write(fd,&cmd,sizeof(cmd));
copy_from_user(&userCmd,buf,count);//参数是void* 会有警告,不用管
//根据值来操作io口,高电平或者低电平
printk("get value\n");
if(userCmd == 1){
printk("SET1\n");
*GPSET0 |= 0x1 << 4;//为什么左移4位,根据以往开发经验
}else if(userCmd == 0){
printk("SET0\n");
*GPCLR0 |= 0x1 << 4;
}else{
printk("undo\n");
}
return 0;
}
static ssize_t pin4_read(struct file *file,const char __user *buf,ssize_t count,loff_t *pin4_fops)//上层调用read,这里就调用pin4write
{
printk("pin4_read\n");
return 0;
}
static struct file_operations pin4_fops = {
.owner = THIS_MODULE,
.open = pin4_open,
.write = pin4_write,
.read = pin4_read,
};
int __init pin4_drv_init(void) //真实驱动入库,insmod 的时候被调用
{
int ret;
printk("insmod driver pin4 success\n");
devno = MKDEV(major,minor); //创建设备号:参数是主设备号和次设备号
ret = register_chrdev(major,module_name,&pin4_fops); //注册驱动告诉内核,把这个驱动加入到内核,参数分别是:主设备号,模组名"pin4",操作函数结构体
pin4_calss = class_create(THIS_MODULE,"myfirstdemo"); //让代码在/dev下自动生成设备
//也可以手动生成sudo mknod abc 8 1 //主动生成设备,主设备号是8,次设备号是1,名字是abc,查看是否生成了该设备ls -l
pin4_calss_dev = device_create(pin4_calss,NULL,devno,NULL,module_name); //创建设备文件
/*
static dev_t devno; //设备号
static int major = 231; //主设备号
static int minor = 0; //次设备号
static char *module_name = "pin4"; //模块名*/
//这里是加入链表时的GPFSEL0
//把物理地址映射到虚拟地址
GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);//物理地址转换成虚拟地址,io口寄存器映射成普通内存单元进行访问
GPSET0 = (volatile unsigned int *)ioremap(0x3f20001C,4);//volatile和强转是一个整体要一起写进括号,volatile一定要系统如实写入数据
GPCLR0 = (volatile unsigned int *)ioremap(0x3f200028,4);//这里的4 ,是size_t 4,4个字节32位
return 0;
}
void __exit pin4_drv_exit(void)
{
iounmap(GPFSEL0);//解除虚拟地址的映射
iounmap(GPSET0);
iounmap(GPCLR0);
device_destroy(pin4_calss,devno);//销毁设备
class_destroy(pin4_calss);//销毁类
unregister_chrdev(major,module_name); //卸载驱动
}
module_init(pin4_drv_init); //入口,内核加载该驱动的时候,这个宏会被调用
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");
在树莓派可以用gpio readall查看对io的操作是否成功

本文介绍了如何在Linux环境下交叉编译树莓派的设备驱动代码,包括驱动代码的基本框架、编译配置、驱动加载与卸载。详细讲解了地址总线、物理地址和虚拟地址的概念,以及树莓派BCM2835芯片手册中的寄存器操作。同时,提到了驱动加载后的测试程序编写和权限设置。
1378

被折叠的 条评论
为什么被折叠?



