目录
一、多节点
1、什么是多节点、多节点的意义
多节点:一个设备对应一个节点文件(设备文件)
意义:增加硬件的独立性
2、多节点的实现方法
在一个驱动文件下实现多节点
只要是靠Linux2.6下的 cdev_add
cdev 支持连续注册多个设备文件!
3、多节点下的流水灯
#include "linux/kernel.h"
#include "linux/module.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include "linux/gpio.h"
#include "linux/device.h"
dev_t devnum;
struct cdev cdev;
struct file_operations ops;
struct class * cls;
//四个设备文件共用了一个打开接口和关闭接口
/*
现在你再上层调用 open
不管你打开的是 LED1 还是LED2 亦或是LED3
那么他都会调用 myled_open
请问我如何区分谁再打开造成底层的调用
*/
int myled_open(struct inode * inode, struct file * file)
{
/*
inode 传参里面有个成员变量
i_rdev -- 记录的是调用底层open
打开设备的设备号!
*/
if(inode->i_rdev == devnum)//LED1
{
gpio_set_value(EXYNOS4X12_GPM4(0),0);
}else if(inode->i_rdev == devnum+1 ) //LED2
{
gpio_set_value(EXYNOS4X12_GPM4(1),0);
}else if(inode->i_rdev == devnum+2 ) //LED3
{
gpio_set_value(EXYNOS4X12_GPM4(2),0);
}else if(inode->i_rdev == devnum+3 ) //LED4
{
gpio_set_value(EXYNOS4X12_GPM4(3),0);
}
return 0;
}
int myled_close(struct inode * inode, struct file * file)
{
if(inode->i_rdev == devnum)//LED1
{
gpio_set_value(EXYNOS4X12_GPM4(0),1);
}else if(inode->i_rdev == devnum+1 ) //LED2
{
gpio_set_value(EXYNOS4X12_GPM4(1),1);
}else if(inode->i_rdev == devnum+2 ) //LED3
{
gpio_set_value(EXYNOS4X12_GPM4(2),1);
}else if(inode->i_rdev == devnum+3 ) //LED4
{
gpio_set_value(EXYNOS4X12_GPM4(3),1);
}
return 0;
}
static int __init led_init(void)
{
//1:申请四个设备号
int ret = alloc_chrdev_region(&devnum,0,4,"led");
if(ret < 0)
{
printk("申请设备号失败\r\n");
return -EINVAL;
}
//2 :Linux2.6 核心结构体初始化
ops.owner = THIS_MODULE;
ops.open = myled_open;
ops.release = myled_close;
cdev_init(&cdev, &ops);
cdev_add(&cdev,devnum,4);
/*
由于注册设备公用一个
cdev核心结构体
也就是公用一个ops
也就是他们四个设备公用
一套文件接口!!!
*/
//3: 生成设备文件
cls = class_create(THIS_MODULE, "led");
device_create(cls ,NULL, devnum,NULL,"LED1");
device_create(cls ,NULL, devnum+1,NULL,"LED2");
device_create(cls ,NULL, devnum+2,NULL,"LED3");
device_create(cls ,NULL, devnum+3,NULL,"LED4");
/*
你现在的四个设备文件公用一套文件接口!!!
你还是不好做流水灯
*/
return 0;
}
static void __exit led_exit(void)
{
//销毁设备文件
device_destroy(cla,devnum);
device_destroy(cla,devnum+1);
device_destroy(cla,devnum+2);
device_destroy(cla,devnum+3);
//释放类结构体空间
class_destroy(cla);
//删除Linux2.6设备
cdev_del(&cdev);
//释放设备号
unregister_chrdev_region(devnum,4);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
二、内核文件接口
1、open 和 close 文件接口
int (*open) (
struct inode * inode,
struct file * file
);
struct inode
{
i_rdev;//设备的设备号!
i_private;
//设备节点私有数据
//该指针可以指向任何地方
//一般用的比较少
//可以当作传参使用
}
struct file * file{
void *private_data;
//此空指针的用法与 i_private差不多
//也是提供给用户的指针
//可以指向任何地方
//一般多节点中当做传参使用
}
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, loff_t * offt);
file:
他就是 open和close的file指针
主要用 private_data进行传参
给 read 和 write
大部分情况下用于多节点下
区分不同设备的情况!
read的buf:
你的上层的read的目的一定是
读内核的数据!
那么你内核层的read一定在
给用户层写入数据!
而此buf就是上层read传过来的所谓的
空间
* 但是这个read 不能让你直接访问!
内核专用的将数据拷贝到用户层!
#include <linux/uaccess.h>
copy_to_user(
void __user *to,
const void *from,
unsigned long n
);
to-> buf
from:从哪拷贝数据!
n:你要拷贝过去的长度
顺便也解释了
int read(
int fd,
void * buf
int size
);
read(fd,buf,999);
读到的长度谁决定的
内核!
write的buf:
用户层write一定是在给内核层写数据
所以 内核层实际在读取用户层数据
buf
首先它就是用户层传过来的数据!
还是跟read 的buf一样 不能直接操作
只能通过内核函数操作
copy_from_user(
void *to,
const void __user *from,
unsigned long n
);
to:你现在拷贝数据放到哪!
from:拷贝的数据来自哪--buf
n:你要拷贝几个字节呀!
size 内核read/write第三个参数
size:
实际上内核传数据的长度!
offt:
偏移量仅仅适用于块设备文件!
对于字符设备文件而言没有偏移量概念
3、按键的驱动控制LED灯 示例
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
//#include <asm/uaccess.h>
#include <linux/uaccess.h>
/*
按键
GPX3_2 -- KEY1
GPX3_3 -- KEY2
GPX3_4 -- KEY3
GPX3_5 -- KEY4
按键按下低
松开高
*/
dev_t devnum;
struct cdev cdev;
struct file_operations ops;
struct class * cls;
int mykey_open(struct inode * inode, struct file * file)
{
//如果按键没有初始化一般在此处初始化!
file->private_data = &inode->i_rdev;
if(inode->i_rdev == devnum)
{
//gpio_direction_input(EXYNOS4_GPX3(2));
}else if(inode->i_rdev == devnum+1)
{
//gpio_direction_input(EXYNOS4_GPX3(3));
}else if(inode->i_rdev == devnum+2)
{
//gpio_direction_input(EXYNOS4_GPX3(4));
}else if(inode->i_rdev == devnum+3)
{
//gpio_direction_input(EXYNOS4_GPX3(5));
}
return 0;
}
int mykey_close(struct inode * inode, struct file * file)
{
file->private_data = NULL;
return 0;
}
ssize_t mykey_read(struct file * file, char __user * buf, size_t size , loff_t * offt)
{
uint8_t keyvaule = 0;
dev_t * devnump = file->private_data;
if(* devnump == devnum)
{
keyvaule = gpio_get_value(EXYNOS4_GPX3(2));
}else if(* devnump == devnum+1)
{
keyvaule = gpio_get_value(EXYNOS4_GPX3(3));
}else if(* devnump == devnum+2)
{
keyvaule = gpio_get_value(EXYNOS4_GPX3(4));
}else if(* devnump == devnum+3)
{
keyvaule = gpio_get_value(EXYNOS4_GPX3(5));
}
copy_to_user(buf,&keyvaule,1);
return 0;
}
static int __init key_init(void)
{
//1:申请设备号
alloc_chrdev_region(&devnum,0,4, "keydevnum");
//2:初始化Linux2.6 核心结构体
ops.open = mykey_open;
ops.read = mykey_read;
ops.release= mykey_close;
cdev_init(&cdev,&ops);
cdev_add(&cdev,devnum,4);
//3:生成设备文件
cls = class_create(THIS_MODULE,"keyclass");
device_create(cls,NULL,devnum, NULL,"key1");
device_create(cls,NULL,devnum+1, NULL,"key2");
device_create(cls,NULL,devnum+2, NULL,"key3");
device_create(cls,NULL,devnum+3, NULL,"key4");
return 0;
}
static void __exit key_exit(void)
{
}
module_init(key_init);
module_exit(key_exit);
MODULE_LICENSE("GPL");