我们应用程序使用open函数的时候,会调用内核的sys_open函数,然后接下来
1、然后打开普通文件的话会使用文件系统操作硬件,
2、要是打开驱动文件,会使用驱动程序对应的drv_open函数

怎么写驱动程序

我们驱动对应的drv_open等函数写好了,存放在file_operation结构体中
将结构体告诉内核,也就是将结构体通过一个函数注册到内核中(注册的时候会设定主设备号,可自己设定也可以系统分配)
将结构体存放到一个对应的数组中,根据主设备号来存放查找
Linux内核允许多个驱动共享一个主设备号,但更多的设备都遵循一个驱动对一个主设备号的原则
内核维护这一个以主设备号为key的全局哈希表,而哈希表中的数据部分为与该主设备号对应的驱动程序(只有一个次设备)
app打开一个文件,会获得一个整数,这个整数对应一个结构体struct_file.
应用根据主设备号,在数组中找到一个file_operation结构体,这个结构体提供驱动的各种读写函数

struct file {union {struct llist_node fu_llist;struct rcu_head fu_rcuhead;} f_u;struct path f_path;struct inode *f_inode; /* cached value */const struct file_operations *f_op; //各种操作选项/** Protects f_ep_links, f_flags.* Must not be taken from IRQ context.*/spinlock_t f_lock;atomic_long_t f_count;unsigned int f_flags; //open函数传入的参数fmode_t f_mode; //open函数传入的参数struct mutex f_pos_lock;loff_t f_pos;struct fown_struct f_owner;const struct cred *f_cred;struct file_ra_state f_ra;u64 f_version;struct address_space *f_mapping;
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
struct file_opwerations
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 (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);int (*iterate_shared) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);
1、驱动程序

#include #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include /* 1. 确定主设备号 */
static int major = 0; //表示内核自动分配主设备号
static char kernel_buf[1024];//保存app下发的字符串,也就是将用户空间的buf数据传递到这个里面
static struct class *hello_class;#define MIN(a, b) (a < b ? a : b)//取二者最小/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 *//*
加入static 免得污染命名空间
_usr 表示来着用户空间,buf用户空间指针,
size 读多长的数据
struct file *:文件结构体
loff_t *:读取数据的偏移量*/
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_to_user(buf, kernel_buf, MIN(1024, size));//拷贝数据从内核空间到用户空间大小不得超过1024return MIN(1024, size);//返回处理的数据的长度
}static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(kernel_buf, buf, MIN(1024, size));//将用户空间的数据拷贝到内核空间大小不得超过1024字节return MIN(1024, size);
}
//struct inode文件的数据结构
static int hello_drv_open (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}
//struct inode文件的数据结构
static int hello_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 2. 定义自己的file_operations结构体 */
static struct file_operations hello_drv = {.owner = THIS_MODULE,//给结构体成员中的owner赋值.open = hello_drv_open,//将结构体hello_drv_open赋给open属性.read = hello_drv_read,.write = hello_drv_write,.release = hello_drv_close,
};/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
//这就相当于主函数
static int __init hello_init(void)
{int err;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "hello", &hello_drv); /* /dev/hello *///传入操作函数的结构体hello_class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(hello_class);if (IS_ERR(hello_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "hello");return -1;}//创建出设备节点device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */return 0;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit hello_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);device_destroy(hello_class, MKDEV(major, 0));//销毁设备节点class_destroy(hello_class);unregister_chrdev(major, "hello");
}/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(hello_init);//将hello_init修饰成入口函数
module_exit(hello_exit);//将hello_init修饰成出口函数MODULE_LICENSE("GPL");//该驱动程序遵守GPL协议
2、应用程序

#include
#include
#include
#include
#include
#include /** ./hello_drv_test -w abc* ./hello_drv_test -r*/
int main(int argc, char **argv)
{int fd;char buf[1024];int len;/* 1. 判断参数 *///要是参数不对就打印出用法if (argc < 2) {printf("Usage: %s -w \n", argv[0]);printf(" %s -r\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open("/dev/hello", O_RDWR);//打开设备节点hello,这个名字是根据我们的驱动程序给命名的,以读方式打开if (fd == -1){printf("can not open file /dev/hello\n");return -1;}/* 3. 写文件或读文件 *///strcmp函数,要是第一个字符等于第二个字符的话,就会返回0if ((0 == strcmp(argv[1], "-w")) && (argc == 3))//输入的第二个参数是-w,并且还存放第三个参数{len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd, argv[2], len);//将argv[2]写入到fd中else{len = read(fd, buf, 1024); //将fd中的数据读到bufbuf[1023] = '\0';printf("APP read : %s\n", buf);}close(fd);return 0;
}
查看开发板中的所有的驱动程序
cat /proc/devices
查看加载的驱动
lsmod
加载设备驱动
insmod 100ask_led.ko // 装载驱动程序
卸载设备驱动
rmmod drv.ko
查看是否存放hello设备节点
ls /dev/hello
环型电路演变,只要有高电位出发,到低电位停止就可以

当引脚电力不足,使用三极管

只要主芯片1.2v就可以将三极管下面导通,然后上面的3.3v就可以与地面产生电路,要是0v就无法导通三极管
另外一种接法
当左边第一个3.3导通,那个黑点电位就是0

三极管基础
当左边电压大于右边电压就是想当与导通,电流就可以从上面流到下面

当右边电位大于左边电位,三极管导通,电流可以从下流到上


1、使能
2、设置为gpio口
3、设置为输出功能
4、设置电位为高还是地
操作寄存器注意不要影响到其他位
1、读出寄存器的值

2、修改bit0的值为1

3、写回去
要是直接设置data_reg = 1的话,会设置bit0、bit1、bit2的值
使用set_reg函数简单方便

使用clr_reg函数
这两个函数,只有设置为1的位才可以使得实际位生效
CCM时钟控制模块
1、通过设置其中的寄存器使能某个gpio口
IOMUXC IO复用控制器
2、设置其中的某个寄存器来设置某个引脚(gpio)列如:引脚功能(gpio就是其中一种功能)、是输入还是输出、是否使用上拉电阻之类

驱动程序的框架

我们应用程序要read函数通过内核来调用对应驱动程序的read函数,那我们怎么让内核调动对应的驱动程序呢?
内部存放一个数组regsiter_chrdrv,数组中存放对应驱动程序的file_operation函数,数组中的位置就是是主设备号(数组下标)
在我们装载驱动程序的时候,内核会自动调用入口函数,入口函数就会创建将operation函数填充到上面的数组中
在我们入口函数中在填充chrdev数组的时候,调用下面两个函数去自动创建设备节点
在卸载驱动程序的时候,内核会自动调用出口函数,该函数会将operation函数对应的位置给删除
细节
应用程序无法直接访问到驱动程序中的数据,必须使用辅助函数
copy_to_usrcopy_from_user
驱动程序怎么操作硬件
驱动程序无法直接使用硬件寄存器的物理地址,必须使用ioctl映射成一个虚拟地址,然后使用虚拟地址访问寄存器
怎么查看芯片手册来看引脚
0、查看芯片看LED原理图

1、使能

2、设置为GPIO5_3为GPIO

也就是要使得这个寄存器最后三位变成101
3、设置为输出功能

gpio_3 设置为输出,就是在将改寄存器的引脚3设置为1(芯片引脚从0开始的)
4、设置电位为高还是低(低就是点亮,高是熄灭)

o 表示高电位,1表示地电位
代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include static int major;//主设备号
static struct class *led_class; //类可以自动创建设备节点/* registers 定义指向硬件寄存器的指针,通过操作指针控制寄存器*/
//volation 表示不进行优化
/*
引脚设置
1、使能
2、配置成gpio*/
// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14
static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;//将GPIO5_3设置为GOIO模式// GPIO5_GDIR 地址:0x020AC004
static volatile unsigned int *GPIO5_GDIR;//设置引脚是输入还是输出方向//GPIO5_DR 地址:0x020AC000
static volatile unsigned int *GPIO5_DR;//控制引脚输出高电平还是低电平static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{char val;int ret;/* copy_from_user : get data from app */ret = copy_from_user(&val, buf, 1);/* to set gpio register: out 1/0 */if (val){/* set gpio to let led on */*GPIO5_DR &= ~(1<<3); //让bit3等于0}else{/* set gpio to let led off */*GPIO5_DR |= (1<<3); //让bit3等于1}return 1;
}static int led_open(struct inode *inode, struct file *filp)
{/* enable gpio5* configure gpio5_io3 as gpio* configure gpio5_io3 as output */*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 &= ~0xf;//0xf = 1111 ~0xf = 0000//*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 & ~0xf;//清掉后四位就是将后四位与0&*IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 |= 0x5; //后三位101*GPIO5_GDIR |= (1<<3); //设置GPIO5引脚3为1表示输出,也就是芯片的第四个引脚return 0;
}static struct file_operations led_fops = {.owner = THIS_MODULE,.write = led_write,.open = led_open,
};/* 入口函数 */
static int __init led_init(void)
{printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "100ask_led", &led_fops);/* ioremap 将寄存器的物理地址转换成虚拟地址*/// IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 地址:0x02290000 + 0x14IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x02290000 + 0x14, 4);// GPIO5_GDIR 地址:0x020AC004GPIO5_GDIR = ioremap(0x020AC004, 4);//GPIO5_DR 地址:0x020AC000GPIO5_DR = ioremap(0x020AC000, 4);led_class = class_create(THIS_MODULE, "myled");device_create(led_class, NULL, MKDEV(major, 0), NULL, "myled"); /* /dev/myled */return 0;
}static void __exit led_exit(void)
{//清楚掉地址转换的结果iounmap(IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3);iounmap(GPIO5_GDIR);iounmap(GPIO5_DR);device_destroy(led_class, MKDEV(major, 0));class_destroy(led_class);unregister_chrdev(major, "100ask_led");
}module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
将共同的部分写成一个leddrv.c,然后其他不同部分就写不同文件里面

分层思想实现支持多个板子

1、leddrv.c(通用部分)
#include #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include #include "led_opr.h"#define LED_NUM 2/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class; //这个类自动创建出设备节点
struct led_operations *p_led_opr; //创建指向led_operations结构体的指针#define MIN(a, b) (a < b ? a : b) //取最小值/* 3. 实现对应的open/read/write等函数,填入file_operations结构体 */
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* write(fd, &val, 1); */
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;char status; //状态变量struct inode *inode = file_inode(file);int minor = iminor(inode); //获得次设备号printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);err = copy_from_user(&status, buf, 1);//将数据从buf中拷贝到status中/* 根据次设备号和status控制LED */p_led_opr->ctl(minor, status);return 1;
}static int led_drv_open (struct inode *node, struct file *file)
{int minor = iminor(node);printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);/* 根据次设备号初始化LED */p_led_opr->init(minor);return 0;
}static int led_drv_close (struct inode *node, struct file *file)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);return 0;
}/* 2. 定义自己的file_operations结构体 */
static struct file_operations led_drv = {.owner = THIS_MODULE,.open = led_drv_open,.read = led_drv_read,.write = led_drv_write,.release = led_drv_close,
};/* 4. 把file_operations结构体告诉内核:注册驱动程序 */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init led_init(void)
{int err;int i;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led *///驱动程序的名字叫做100ask_ledled_class = class_create(THIS_MODULE, "100ask_led_class");//创建一个生成设置节点的类名字叫做100ask_led_classerr = PTR_ERR(led_class);if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "100ask_led");return -1;}//创建出多个设备节点 for (i = 0; i < LED_NUM; i++)device_create(led_class, NULL, MKDEV(major, i), NULL, "100ask_led%d", i); /* /dev/100ask_led0,1,... */p_led_opr = get_board_led_opr();//获得单板结构体return 0;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数 */
static void __exit led_exit(void)
{int i;printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);// 销毁设备节点for (i = 0; i < LED_NUM; i++)device_destroy(led_class, MKDEV(major, i)); /* /dev/100ask_led0,1,... */device_destroy(led_class, MKDEV(major, 0));class_destroy(led_class);unregister_chrdev(major, "100ask_led");
}/* 7. 其他完善:提供设备信息,自动创建设备节点 */module_init(led_init);
module_exit(led_exit);MODULE_LICENSE("GPL");
2、board.c(单板不同部分)
#include #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "led_opr.h"static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */
{printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);return 0;
}static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
{printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");return 0;
}static struct led_operations board_demo_led_opr = {.init = board_demo_led_init,.ctl = board_demo_led_ctl,
};struct led_operations *get_board_led_opr(void)
{return &board_demo_led_opr;
}
#ifndef _LED_OPR_H //只有后面的存在,就不再往下执行
#define _LED_OPR_Hstruct led_operations {int (*init) (int which); /* 初始化LED, which-哪个LED */ int (*ctl) (int which, char status); /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
};struct led_operations *get_board_led_opr(void);//定义一个指针指向结构体Lled_operations#endif

用结构体来表示某个对象

分离思想
将某个有很多类似操作的写在一起,这样我们根据参数就可以替换不同的步骤了

1、上下分层
将设计硬件的比如
初始化gpio、设置GPIO写成board.c
同用的就写在drv.c里面
2、左右分离
将board.c分成两部分
1、数据配置(资源)
2、硬件操作
为了使得兼容多个驱动,扩展了分离思想

硬件操作都在platform_driver 、资源分配都在platform_device上
platform_device(资源分配)使用那个LED灯
#include "led_resource.h"static struct led_resource board_A_led = {.pin = GROUP_PIN(3,1),
};struct led_resource *get_led_resouce(void)
{return &board_A_led;
}
platform_device结构体中的struct resource结构体(各种各样的LED灯)
ifndef _LED_RESOURCE_H
#define _LED_RESOURCE_H/* GPIO3_0 */
/* bit[31:16] = group */
/* bit[15:0] = which pin */
#define GROUP(x) (x>>16)
#define PIN(x) (x&0xFFFF)
#define GROUP_PIN(g,p) ((g<<16) | (p))struct led_resource {int pin;
};struct led_resource *get_led_resouce(void);#endif
static int board_demo_led_init (int which) /* 初始化LED, which-哪个LED */static int board_demo_led_ctl (int which, char status) /* 控制LED, which-哪个LED, status:1-亮,0-灭 */
设备树
将各种引脚配置参数存放在内核之外,配置文件dts(指定使用那个引脚),
然后将dts编译成dtb传给内核
内核解析dtb文件,构造出一系列的strcut platform_device 这类的

devicr 与drv怎么挂钩的

1、当我们注册一个平台设备,设备就会放在左边链表,注册一个平台drv就会放在右边链表
2、当注册设备或者驱动的时候,都会在对面查找是否有匹配的,匹配成功调用drv函数
3、怎么查找是否匹配成功?
bus结构体

1、先比较左边override非她不嫁,是否与右边的名字一样

![]()
2、比较左边的名字是否在右边id_table支持的范围内

![]()
3、比较左边的名字是否与右边的driver名字一样
![]()
![]()
代码有删减
static int platform_match(struct device *dev, struct device_driver *drv)
{/* When driver_override is set, only bind to the matching driver */if (pdev->driver_override)return !strcmp(pdev->driver_override, drv->name);/* Then try to match against the id table */if (pdrv->id_table)return platform_match_id(pdrv->id_table, pdev) != NULL;/* fall-back to driver name match */return (strcmp(pdev->name, drv->name) == 0);
}

将资源配置文件存放内核之外,我们只需要传入配置文件,内核就可以解析资源分配信息
配置文件是设备树写的
