一,多节点思想
1.什么是多节点
一个设备对应一个节点文件(设备文件)
2.多节点实现的方法
linux2.6的连续注册
class类的生成多个设备文件
3.一个LED灯的多节点的代码
#include "linux/kernel.h"
#include "linux/module.h"//基本必须头文件
#include "linux/fs.h"//申请设备号,内和操作结构体
#include "linux/cdev.h"//linux2.6开发头文件
#include "linux/of.h"//设备树接口相关头文件
#include "linux/of_gpio.h" //设备树 GPIO相关接口
#include "linux/gpio.h"//GPIO子系统头文件
#include "linux/platform_device.h"//平台设备总线头文件
dev_t *devnum;//首设备号
struct cdev *cdev;//linux2.6结构体
struct file_operations *ops;//内核文件操作集合
struct class *cls;//类结构体
//自定义LED灯信息结构体
struct xydled_info {
int gpio_num;//gpio的编号
enum of_gpio_flags active_flags;//GPIO有效电平
char led_name[32];//存储led灯设备名
struct xydled_info * next; //下一项
};
struct xydled_info * head; //头节点
int count=0;//LED灯数量
struct of_device_id xydled_table={
.compatible = "xyd_led1",
};
struct platform_driver * platdrv;
/*
你不管哪个LED灯被打开都会调用 这个open!
*/
int xydled_open(struct inode * inode, struct file * f)
{
struct xydled_info *tmp =head;
for(int i=0;i<count;i++)
{
if(inode->i_rdev==*devnum+i)//LEDi看谁设备号相同
{
//操作第i灯
gpio_set_value(tmp->gpio_num,!tmp->active_flags);
}
tmp=tmp->next;
}
return 0;
}
int xydled_close(struct inode * inode, struct file * f)
{
struct xydled_info *tmp = head;
for(int i=0;i<count;i++)
{
if(inode->i_rdev==*devnum+i)//LEDi//看谁设备号相同
{
//操作第 i 灯
gpio_set_value(tmp->gpio_num,tmp->active_flags);
}
tmp= tmp->next;
}
return 0;
}
//新入口
int xydled_probe(struct platform_device * devp)
{
//1.获取所有GPIO信息
struct xydled_info * tmp;
while(1)
{
tmp = kzalloc(sizeof(struct xydled_info), GFP_KERNEL);//开辟一个结构体空间
tmp->next = NULL;//链表下一项指向空
//获取GPIO的有效电平和GPIO编号 存入到结构体信息里面
tmp->gpio_num = of_get_named_gpio_flags(devp->dev.of_node,"led_gpio",count,&tmp->active_flags);
if(tmp->gpio_num < 0)//没有信息获取
{
kfree(tmp);//释放空间
break;
}
sprintf(tmp->led_name,"xydled%d",count+1);
if(count == 0)//这是我的第一个节点
{
head=tmp;//头节点即是第一个存信息节点
}else //不是头节点
{
struct xydled_info * tempp = head;
while(tempp->next!=NULL)
tempp=tempp->next;//遍历尾节点
tempp->next=tmp;//新节点接到尾节点
}
count ++;
}
//2: 注册GPIO引脚
tmp = head;
for(int i=0;i<count;i++)
{
gpio_request(tmp->gpio_num, tmp->led_name);//申请引脚
gpio_direction_output(tmp->gpio_num,tmp->active_flags);//设置输出且设置初始电平无效状态
tmp = tmp->next;
}
//3:多节点
//3.1 申请设备号
devnum = kzalloc(sizeof(dev_t), GFP_KERNEL);
alloc_chrdev_region(devnum,0,count,"xydled");
//3.2: linux2.6的结构体的初始化
cdev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
ops = kzalloc(sizeof(struct file_operations),GFP_KERNEL);
ops->owner = THIS_MODULE;
ops->open = xydled_open;
ops->release = xydled_close;
cdev_init(cdev,ops);
//3.3:添加内核
cdev_add(cdev,*devnum,count);//连续添加设备
//3.4:生成一个类设备文件
cls = class_create(THIS_MODULE,"xydled");
//3.5: 创建设备节点
tmp = head;
for(int i=0;i<count;i++)
{
device_create(cls,NULL,*devnum + i,NULL,tmp->led_name);
tmp = tmp->next;
}
return 0;
}
//新出口
int xydled_remove(struct platform_device *devp)
{
return 0;
}
//加载函数
static int __init xydled_init(void)
{
platdrv = kzalloc(sizeof(struct platform_driver), GFP_KERNEL);//空间开辟
platdrv->probe = xydled_probe;//新入口
platdrv->remove = xydled_remove;//新出口
platdrv->driver.of_match_table = &xydled_table;
platdrv->driver.name = "xydled_driver";//驱动名
return platform_driver_register(platdrv);//注册驱动层信息 主要是想匹配设备树信息
}
//卸载函数
static void __exit xydled_exit(void)
{
}
module_init(xydled_init);//加载函数的声明
module_exit(xydled_exit);//卸载函数的声明
MODULE_LICENSE("GPL");//开源协议的声明
二,内核接口的write和read
1.内核层和用户层之间的数据交换
如果要获取传感器的数据,我们需要设置传感器的工作模式,需要我们用户层和内核层做数据交换。从而学完read和write能够驱动内容更多。
2.内核read和write详解
函数原型:
ssize_t (*read) ( struct file * file, char __user * buf, size_t size, loff_t * offt );
ssize_t (*write) ( struct file * file, const char __user * buf, size_t size, loff_t * offt );
函数参数:
file: 内核所有的传参都是内核传递 调用这个函数的时候 就会传递内核的所带的信息 其中 file 就是内核的传参之一。
buf: 缓冲区。
如果是 read 的 buf,上层调用的 read(fd,buf,len) 就是上层传递的 buf,上层调用 read 本意是什么->读取内容!
读到传递 buf->内核层的 buf 所以说,内核层 read 的 buf 我们应该往里填充数据 内核层的 read 实际上在写 buf 数据->传递 上层了! 也就是上层的 read 数据来源于内核层的 read 的 buf! copy_to_user()->写入到内核层 buf->传递上层
如果你是 write 的 buf,上层调用 write(fd,buf,len) 上层本意->写入数据,数据在 buf。
最终 buf->内核层 实际上内核层的 write 在 读取上层传递过来的 buf(数据)
copy_from_user();->拷贝上层传递 buf 数据
size:
上层传递的数据的大小 read 代表上层期望读取数据长度
write 代表上层传递的数据的长度
offt:
一般开发字符设备无用,偏移量
3.按键的驱动
#include "linux/kernel.h"
#include "linux/module.h"//基本必须头文件
#include "linux/fs.h"//申请设备号,内和操作结构体
#include "linux/cdev.h"//linux2.6开发头文件
#include "linux/of.h"//设备树接口相关头文件
#include "linux/of_gpio.h" //设备树 GPIO相关接口
#include "linux/gpio.h"//GPIO子系统头文件
#include "linux/platform_device.h"//平台设备总线头文件
#include "linux/miscdevice.h"
#include "asm/uaccess.h"
#include "linux/uaccess.h"
enum of_gpio_flags key1_active;
int key1_gpionum;
struct miscdevice mymisc;
struct file_operations ops;
int mykey_open(struct inode * inode, struct file * f)
{
return 0;
}
int mykey_close(struct inode * inode, struct file * f)
{
return 0;
}
ssize_t mykey_read(struct file * file, char __user * buf, size_t
size, loff_t *ppos)
{
uint8_t value=0;
//4.读取GPIO的值
value=gpio_get_value(key1_gpionum);
//把value的值拷贝给上层
int ret=copy_to_user(buf,&value,1);
ret=ret;
return 0;
}
//新入口
int mykey_probe(struct platform_device * devp)
{
//1.有设备树-》获取GPIO的信息
key1_gpionum = of_get_named_gpio_flags(devp->dev.of_node,"xyd_gpios",0,&key1_active);
//2.申请GPIO
gpio_request(key1_gpionum,"xydkey1");
//3.引脚设置为 输入模式
gpio_direction_input(key1_gpionum);
ops.owner=THIS_MODULE;
ops.open=mykey_open;
ops.release=mykey_close;
ops.read=mykey_read;
mymisc.minor=255;
mymisc.name="xydkey1";
mymisc.fops=&ops;
return misc_register(&mymisc);
}
//新出口
int mykey_remove(struct platform_device *devp)
{
return 0;
}
struct platform_driver drv;
struct of_device_id mykey_tale={
.compatible = "xydkey",
};
//加载函数
static int __init mykey_init(void)
{
drv.probe=mykey_probe;
drv.remove=mykey_remove;
drv.driver.name="mykey_driver";
drv.driver.of_match_table=&mykey_tale;
return platform_driver_register(&drv);
}
//卸载函数
static void __exit mykey_exit(void)
{
}
module_init(mykey_init);//加载函数的声明
module_exit(mykey_exit);//卸载函数的声明
MODULE_LICENSE("GPL");//开源协议的声明
标签:tmp,struct,接口,内核,xydled,gpio,include,buf,节点
From: https://blog.csdn.net/hcc_v/article/details/142964708