APP-》C library调用open产生软中断系统调用,中断号0x80-》汇编sys_call-》VFS的sys_open-》内核空间,设备驱动的open-》硬件
驱动实现有3条路线,file_operations和attribute,驱动模型(bus总线)
相关命令
查看驱动相关
#include // module_init module_exit
#include // __init __exit
/****************************************
模块安装函数
__init修饰符,是一个宏,作用就是把这个函数放入特定的段,驱动加载完后就会释放这个段空间
宏原型 #define __init __section(.init.text)
位置:include/linux/init.h
***************************************/
static int __init chrdev_init(void)
{
printk(KERN_INFO "chrdev_init helloworld init\n");
//printk("<7>" "chrdev_init helloworld init\n");
//printk("<7> chrdev_init helloworld init\n");
return 0;
}
// 模块下载函数
static void __exit chrdev_exit(void)
{
printk(KERN_INFO "chrdev_exit helloworld exit\n");
}
module_init(chrdev_init);
module_exit(chrdev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("ZhangXiaowei"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
file_operations结构体,控制硬件的操作方法
新旧接口区别
旧接口是新接口的封装
__register_chrdev_region
cdev_alloc
cdev_add
新接口,可以一次性注册多个次设备
旧接口
新接口
宏MKDEV,MAJOR,MINOR
合并,拆分主号,拆分次号
注册(分2步,分配主次设备号,注册设备驱动)
分配主次设备号
注册设备驱动
struct cdev结构体
struct cdev {
struct kobject kobj; // 内嵌的内核对象.
struct module *owner; //该字符设备所在的内核模块的对象指针.
const struct file_operations *ops; //该结构描述了字符设备所能实现的方法,即file_operations.
struct list_head list; //用来将已经向内核注册的所有字符设备形成链表.
dev_t dev; //字符设备的设备号,由主设备号和次设备号构成.
unsigned int count; //隶属于同一主设备号的次设备号的个数.
} __randomize_layout;
cdev_alloc,实例化结构体变量
cdev_init,初始化cdev结构体
cdev_add,注册设备驱动
注销(2步)
查看 cat /proc/devices
2种方法(手动,自动)
查看
ls /dev/xxx
/sys/class/xxx/xxx
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WxG93nXu-1681025761679)(C:\Users\戴尔\AppData\Roaming\Typora\typora-user-images\1680919245694.png)]
#include
#include
#include
#include
#include
#include
#include
#include
static struct cdev *pin1;//注册设备
static struct cdev *pin2;
static struct class *pin_class;//类
static struct device *pin1_class_dev;//设备
static struct device *pin2_class_dev;
static dev_t devno;
static int major = 231;
static int minor = 0;
//在内核源码查找struct file_operations看结构体成员,添加用到的函数
static const struct file_operations pin1_fops = {
.owner = THIS_MODULE
};
static const struct file_operations pin2_fops = {
.owner = THIS_MODULE
};
static int __init pin_init(void)//驱动入口
{
printk("insmod driver pin4 success\n");
devno = MKDEV(major,minor);//制作合并主、次设备号
alloc_chrdev_region(&devno, 0, 2,"pin"); //分配2个次设备号
pin1 = cdev_alloc(); //注册设备
cdev_init(pin1,&pin1_fops);
cdev_add(pin1, MKDEV(major,0), 2);
pin2 = cdev_alloc();
cdev_init(pin2,&pin2_fops);
cdev_add(pin2, MKDEV(major,1), 2);
pin_class = class_create(THIS_MODULE,"myfirstdemo");//代码自动生成类
pin1_class_dev = device_create(pin_class,NULL,MKDEV(major,0),NULL, "pin1");//创建设备文件
pin2_class_dev = device_create(pin_class,NULL,MKDEV(major,1),NULL, "pin2");//创建设备文件
return 0;
}
static void __exit pin_exit(void)
{
device_destroy(pin_class,MKDEV(major,0));//销毁设备
device_destroy(pin_class,MKDEV(major,1));//销毁设备
class_destroy(pin_class);//销毁类
cdev_del(pin1); //销毁设备驱动
cdev_del(pin2);
unregister_chrdev_region(devno,2); //销毁主次设备号
printk("insmod driver pin4 exit\n");
}
module_init(pin_init);//入口,是个宏
module_exit(pin_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL v2"); // 描述模块的许可证
MODULE_AUTHOR("ZhangXiaowei"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
虚拟地址映射
静态映射
内核硬编码,用不用都在那里,开机建立映射表,关机销毁
map开头的头文件,直接使用虚拟地址操作寄存器
本质是一个结构体数组,数组中每一个元素就是一个映射,这个映射描述了一段物理地址到虚拟地址之间的映射。这个结构体数组所记录的几个映射关系被ictable init所使用,该函数负责将这个结构体数组格式的表建立成MMU所能识别的页表映射关系,这样在开机后可以直接使用相对应的虚拟地址来访问对应的物理地址。
开机时调用映射表建立函数
动态映射(查看数据手册,找到寄存器物理地址)
实现file_operations结构体,的操作方法(write,read函数)
指针方式操作
*((volatile unsigned int *)gpg0con)
内核接口
注意,写寄存器要考虑所有寄存器值
static ssize_t pin45_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int userCmd;
unsigned int t = 0;
//获取上层write值
copy_from_user(&userCmd,buf,count);
printk("get value\n");
//根据值操作io口
if(userCmd == 1){
t |= 1<<4;
*GPSET45 = t;
printk("set 1\n");
}else if(userCmd == 0){
t |= 1<<4;
*GPCLR45 = t;
printk("set 0\n");
}else{
printk("undo\n");
}
return 0;
}
注意:驱动代码要简洁,占用时间短
加权限
sudo chmod 666 /dev/pin4
#include
#include
#include
#include
#include
#include
#include
#include
static struct cdev *pin1;//注册设备
static struct cdev *pin2;
static struct class *pin_class;//类
static struct device *pin1_class_dev;//设备
static struct device *pin2_class_dev;
static dev_t devno;
static int major = 231;
static int minor = 0;
#define FSEL 0x3f200000
#define SET0 0x3f20001C
#define CLR0 0x3f200028
typedef struct GPFSEL{
volatile unsigned int GPFSEL0;
volatile unsigned int GPFSEL1;
volatile unsigned int GPFSEL2;
}gpfsel;
gpfsel *pgpfsel = NULL;
volatile unsigned int* GPSET0 = NULL;
volatile unsigned int* GPCLR0 = NULL;
static ssize_t pin1_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
printk("pin1_read\n");
return 0;
}
static ssize_t pin2_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
printk("pin2_read\n");
return 0;
}
static int pin1_open(struct inode * inode, struct file * filp)
{
pgpfsel->GPFSEL0 &= ~(6<<18);
pgpfsel->GPFSEL0 |= 1<<18;
printk("pin1_open\n");//内核的打印函数
return 0;
}
static int pin2_open(struct inode * inode, struct file * filp)
{
pgpfsel->GPFSEL1 &= ~(6<<9);
pgpfsel->GPFSEL1 |= 1<<9;
printk("pin2_open\n");//内核的打印函数
return 0;
}
static ssize_t pin1_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int userCmd;
unsigned int t = 0;
//获取上层write值
copy_from_user(&userCmd,buf,count);
printk("get value1\n");
//根据值操作io口
if(userCmd == 1){
t |= 1<<6;
*GPSET0 = t;
printk("set 1\n");
}else if(userCmd == 0){
t |= 1<<6;
*GPCLR0 = t;
printk("set 0\n");
}else{
printk("undo\n");
}
return 0;
}
static ssize_t pin2_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
int userCmd;
unsigned int t = 0;
//获取上层write值
copy_from_user(&userCmd,buf,count);
printk("get value2\n");
//根据值操作io口
if(userCmd == 1){
t |= 1<<13;
*GPSET0 = t;
printk("set 1\n");
}else if(userCmd == 0){
t |= 1<<13;
*GPCLR0 = t;
printk("set 0\n");
}else{
printk("undo\n");
}
return 0;
}
//在内核源码查找struct file_operations看结构体成员,添加用到的函数
static const struct file_operations pin1_fops = {
.owner = THIS_MODULE,
.write = pin1_write,//函数指针
.open = pin1_open,
.read = pin1_read
};
static const struct file_operations pin2_fops = {
.owner = THIS_MODULE,
.write = pin2_write,//函数指针
.open = pin2_open,
.read = pin2_read
};
static int __init pin_init(void)//驱动入口
{
struct resource *r1;
printk("insmod driver pin4 success\n");
devno = MKDEV(major,minor);//制作合并主、次设备号
alloc_chrdev_region(&devno, 0, 2,"pin"); //分配2个次设备号
pin1 = cdev_alloc(); //注册设备
cdev_init(pin1,&pin1_fops);
cdev_add(pin1, MKDEV(major,0), 2);
pin2 = cdev_alloc();
cdev_init(pin2,&pin2_fops);
cdev_add(pin2, MKDEV(major,1), 2);
pin_class = class_create(THIS_MODULE,"myfirstdemo");//代码自动生成类
pin1_class_dev = device_create(pin_class,NULL,MKDEV(major,0),NULL, "pin1");//创建设备文件
pin2_class_dev = device_create(pin_class,NULL,MKDEV(major,1),NULL, "pin2");//创建设备文件
/*r1 = request_mem_region(FSEL,sizeof(gpfsel), "pin");
if (!r1)
printk("pgpfsel exit\n");*/
pgpfsel = ioremap(FSEL,sizeof(gpfsel));
//request_mem_region(SET0,4, "xwGPIO1");
//request_mem_region(CLR0,4, "xwGPIO2");
GPSET0 = (volatile unsigned int *)ioremap(SET0,4);
GPCLR0 = (volatile unsigned int *)ioremap(CLR0,4);
return 0;
}
static void __exit pin_exit(void)
{
iounmap(GPCLR0);
iounmap(GPSET0);
iounmap(pgpfsel);
printk("iounmap exit\n");
//release_mem_region(CLR0,4);
//release_mem_region(SET0,4);
release_mem_region(FSEL,sizeof(gpfsel));
printk("release_mem_region exit\n");
device_destroy(pin_class,MKDEV(major,0));//销毁设备
device_destroy(pin_class,MKDEV(major,1));//销毁设备
class_destroy(pin_class);//销毁类
cdev_del(pin1); //销毁设备驱动
cdev_del(pin2);
unregister_chrdev_region(devno,2); //销毁主次设备号
printk("insmod driver pin exit\n");
}
module_init(pin_init);//入口,是个宏
module_exit(pin_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL v2"); // 描述模块的许可证
MODULE_AUTHOR("ZhangXiaowei"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
分离式模块化编译
#ubuntu的内核源码树,如果要编译在ubuntu中安装的模块就打开这3个
#KERN_VER = $(shell uname -r)
#KERN_DIR = /lib/modules/$(KERN_VER)/build
# 开发板的linux内核的源码树目录
KERN_DIR = /home/xw/xiaowei/linux-rpi-4.14.y
#obj-m := module_test.o
#modulename-objs := file1.o file2.o 有多个文件编译方式
obj-m += module_test.o #模块化,y编入,n去除,m模块化
#进入到源码树目录下编译模块,然后把生成文件拷贝到原目录
#M=’pwd’代表将编译完的.ko文件放回到当前目录下
all:
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- KERNEL=kernel7 O=output/ -C $(KERN_DIR) M=`pwd` modules
#这里有问题,去除不了
.PHONY: clean
clean:
make O=output/ -C $(KERN_DIR) M=`pwd` modules clean
编译进内核
只在sys/class的具体类下面,创建文件,操作文件
每一类都有成熟,标准的,典型的驱动框架
内核工程师做一部分,驱动做一部分
功能相同的封装出来,不同的驱动工程师做
misc驱动框架
资源使用前必须申请,例如中断号,内存
特定接口,数据结构是驱动的直接体现
结构体struct led_classdev
name:类名myled
max_bringhtness:最大数
brightness:读
brightness_set:写方法,这个函数控制硬件
注册
注销
代码在gpiolib库部分
目录/sys/class/leds/出现xxx
加权限777
cat,echo操作文件(相当于操作硬件)
cat执行了 brightness_show方法
最终执行brightness执行的函数
echo执行 brightness_store方法
echo 1 > xxx
brightness_set函数
设备,驱动分离
算法,数据分离
为了设备驱动相遇probe执行
bus总线对象
不需要自己创建,开机的时候自动创建。结构体对象
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
linux内核自动匹配设备驱动,匹配规则
device设备对象
为驱动提供硬件信息,描述性内容。结构体对象
struct platform_device {
const char *name; //用于做匹配
int id; // 一般都是直接给-1
struct device dev; // 继承了device父类
u32 num_resources; // 资源的个数
struct resource *resource; // 资源:包括了一个设备的地址和中断
}
注册和注销
int platform_device_register(struct platform_device * pdev);
void platform_device_unregister(struct platform_device * pdev);
driver驱动对象
实现功能接口,动作性描述。结构体对象
struct platform_driver {
int (*probe)(struct platform_device *); //匹配成功之后被调用的函数
int (*remove)(struct platform_device *);//device移除的时候调用的函数
struct device_driver driver; //继承了driver父类
const struct platform_device_id *id_table; //如果driver支持多个平台,在列表中写出来
}
注册和注销
int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv)
应用层操作驱动有2条路,/dev设备文件,和属性文件/sys,input使用的是/dev
核心层
设备驱动层
事件驱动层
evdev.c
input_report_key,上报应用层
input_register_handler
input_register_handle
设备和handler匹配
成功注册驱动,生成文件/dev/input/xxx
应用层
驱动
验证使用
/dev/input/下多出event0
/sys/bus/platform/devices出现设备fire
/sys/bus/platform/drivers出现驱动fire
数据+心跳包
驱动
#include
#include
#include
#include
#include
#include
#include
#define GPIO_FIRE 17 //io口号,可以使用命令gpio read
static struct input_dev *input; //指针,后面动态分配内存
static struct timer_list timer; //定时器结构体
static int history; //记录上次io值
//定时器处理函数
static void fire_timer_handler(unsigned long data)
{
int flag;
flag = gpio_get_value(GPIO_FIRE);
if(flag != history){ //和上次值比较
if(flag){
input_report_key(input, KEY_OK, 1); //上报应用层
}else{
input_report_key(input, KEY_OK, 0);
}
history = flag;
}
input_sync(input); //同步包
mod_timer(&timer, jiffies + HZ/100); //更新定时器
}
//匹配成功注册设备
static int fire_button_probe(struct platform_device *pdev)
{
int error;
error = gpio_request(GPIO_FIRE, "GPIO_0_FIRE"); //申请io
if(error){
printk("fire.c: request gpio GPIO_0 fail");
}
gpio_direction_input(GPIO_FIRE); //输入模式
history = gpio_get_value(GPIO_FIRE);
input = input_allocate_device(); //输入设备结构体,实例化
if (!input)
{
printk(KERN_ERR "fire.c: Not enough memory\n");
error = -ENOMEM;
goto err_free_mem;
}
//填充结构体
set_bit(EV_KEY, input->evbit); //按键类型
set_bit(KEY_OK, input->keybit); //哪一个按键
//注册
error = input_register_device(input);
if (error)
{
printk(KERN_ERR "fire.c: Failed to register device\n");
goto err_free_device;
}
//定时器
init_timer(&timer);
timer.function = fire_timer_handler; //处理函数
timer.expires = jiffies + (HZ/100); //定时时间
add_timer(&timer); 启动
return 0;
//倒影式处理错误
err_free_device:
input_free_device(input);
err_free_mem:
gpio_free(GPIO_FIRE);
return error;
}
//注销
static int fire_button_remove(struct platform_device *dev)
{
del_timer(&timer);
input_unregister_device(input);
gpio_free(GPIO_FIRE);
return 0;
}
//设备
static struct platform_device fire_device_button = {
.name = "fire",
.id = -1,
};
//驱动
static struct platform_driver fire_button_device_driver = {
.probe = fire_button_probe,
.remove = fire_button_remove,
.driver = {
.name = "fire",
.owner = THIS_MODULE,
},
};
//平台总线
static int __init button_init(void)
{
platform_device_register(&fire_device_button);
return platform_driver_register(&fire_button_device_driver);
}
static void __exit button_exit(void)
{
platform_driver_unregister(&fire_button_device_driver);
platform_device_unregister(&fire_device_button);
}
module_init(button_init);
module_exit(button_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("xiaowei");
MODULE_DESCRIPTION("fire");
应用层
#include
#include
#include
#include
#include
#include
#define DEVICE_KEY "/dev/input/event1"
#define DEVICE_MOUSE "/dev/input/event3"
#define X210_KEY "/dev/input/event1"
int main(void)
{
int fd = -1, ret = -1;
struct input_event ev;
// 打开驱动文件
fd = open(X210_KEY, O_RDONLY);
if (fd < 0)
{
perror("open");
return -1;
}
while (1)
{
// 初始化事件结构体
memset(&ev, 0, sizeof(struct input_event));
ret = read(fd, &ev, sizeof(struct input_event));
if (ret != sizeof(struct input_event))
{
perror("read");
close(fd);
return -1;
}
// 输出解析
printf("-------------------------\n");
printf("type: %hd\n", ev.type); //按键类型
printf("code: %hd\n", ev.code); //哪一个按键
printf("value: %d\n", ev.value); //值
printf("\n");
}
// µÚ4²½£º¹Ø±ÕÉ豸
close(fd);
return 0;
}
gpiolib库的作用是对所有的gpio实行统一管理,因为驱动在工作的时候,会出现好几个驱动共同使用同一个gpio的情况,这样会造成混乱, 所以内核提供了一些方法来管理gpio资源
目录/sys/class/gpio/有很多gpio设备文件
文件目录drivers/gpio/gpiolib.c
接口函数
1. gpio_request/gpio_free: 申请释放,返回0成功
参数gpio号,名字随便填
2. gpio_request_one/gpio_request_array多个同时申请
3. gpio_direction_input/gpio_direction_out:设置gpio输入/输出模式
gpio号,高低电平
4. gpio_get_value/gpio_set_value:读取设置gpio的值
gpio号,高低电平
5. gpiochip_is_request:判断是否占用gpio
6. gpio_to_desc: 获取gpio实例
7. gpio_to_irq:获取gpio中断号,方便注册中断
查看gpio使用情况
框架+gpio
#include // module_init module_exit
#include // __init __exit
#include
#include
#include
#include
#include
#define GPIO_LED1 17
#define X210_LED_OFF 0 // X210中LED是正极接电源,负极节GPIO
#define X210_LED_ON 1 // 所以1是灭,0是亮
static struct led_classdev mydev1; // 定义结构体变量
// 这个函数就是要去完成具体的硬件读写任务的
static void s5pv210_led1_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
printk(KERN_INFO "s5pv210_led1_set\n");
//writel(0x11111111, GPJ0CON);
// 在这里根据用户设置的值来操作硬件
// 用户设置的值就是value
if (value == LED_OFF)
{
// 用户给了个0,希望LED灭
//writel(0x11111111, GPJ0CON);
// 读改写三部曲
//writel((readl(GPJ0DAT) | (1<<3)), GPJ0DAT);
gpio_set_value(GPIO_LED1, X210_LED_OFF);
}
else
{
// 用户给的是非0,希望LED亮
//writel(0x11111111, GPJ0CON);
//writel((readl(GPJ0DAT) & ~(1<<3)), GPJ0DAT);
gpio_set_value(GPIO_LED1, X210_LED_ON);
}
}
static int __init s5pv210_led_init(void)
{
// 用户insmod安装驱动模块时会调用该函数
// 该函数的主要任务就是去使用led驱动框架提供的设备注册函数来注册一个设备
int ret = -1;
// 在这里去申请驱动用到的各种资源,当前驱动中就是GPIO资源
if (gpio_request(GPIO_LED1, "led1_gpio_0"))
{
printk(KERN_ERR "gpio_request failed\n");
}
else
{
// 设置为输出模式,并且默认输出1让LED灯灭
gpio_direction_output(GPIO_LED1, 1);
}
// led1
mydev1.name = "led1";
mydev1.brightness = 0;
mydev1.brightness_set = s5pv210_led1_set;
ret = led_classdev_register(NULL, &mydev1);
if (ret < 0) {
printk(KERN_ERR "led_classdev_register failed\n");
return ret;
}
return 0;
}
static void __exit s5pv210_led_exit(void)
{
led_classdev_unregister(&mydev1);
gpio_free(GPIO_LED1);
}
module_init(s5pv210_led_init);
module_exit(s5pv210_led_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston <[email protected]>"); // 描述模块的作者
MODULE_DESCRIPTION("s5pv210 led driver"); // 描述模块的介绍信息
MODULE_ALIAS("s5pv210_led"); // 描述模块的别名信息
kobject结构体
位置:Linux/kobject.h
这个结构体相当于一个总基类,被其他结构体所继承。
所以其他结构体当中包含这个基类。
struct kobject {
const char *name; //kobject的名字,且作为一个目录的名字
struct list_head entry; //连接下一个kobject结构
struct kobject *parent; //指向父亲kobject结构体,如果父设备存在
struct kset *kset; //指向kset集合
struct kobj_type *ktype; //指向kobject的属性描述符
struct sysfs_dirent *sd; //对应sysfs的文件目录
struct kref kref; //kobject的引用计数
unsigned int state_initialized:1; //表示该kobject是否初始化
unsigned int state_in_sysfs:1; //表示是否加入sysfs中
unsigned int state_add_uevent_sent:1;
unsigned int state_remove_uevent_sent:1;
unsigned int uevent_suppress:1;
};
各对象最基本单元,提供:每个对象,都会包含一个kobject结构体类
每一个在内核中注册的kobject对象都对应于sysfs文件系统中的一个目录(class目录),而不是文件。
kobj_type结构体
struct kobj_type {
void (*release)(struct kobject *kobj);//释放kobject和其占用的函数
const struct sysfs_ops *sysfs_ops; //操作一个属性数组的方法
struct attribute **default_attrs; //属性数组的方法
const struct kobj_ns_type_operations *(*child_ns_type)(struct kobject *kobj);
const void *(*namespace)(struct kobject *kobj);
};
每一个kobject都需要绑定一个ktype来提供相应功能
sysfs_ops
,提供该对象在sysfs中的操作方法(show和store)
struct sysfs_ops
{
ssize t (*show) (struct kobject *, struct attribute *, char *);/*读属性操作函数*/
ssize t (*store) (struct kobject *,struct attribute *,const char *buf, size_t count);/*写属性操作函数*/
}
attribute
,提供在sysfs中以文件形式存在的属性,其实就是应用接口
struct attribute {
const char *name; //属性的名字
struct module *owner;//指向用于该属性的模块,已经不常使用
mode_t mode; //属性读写权限
};
kset结构体
struct kset {
struct list_head list; //连接链表
spinlock_t list_lock; //链表的自旋锁
struct kobject kobj; //内嵌的kobject结构体,说明kset本身也是一个目录
const struct kset_uevent_ops *uevent_ops; //热插拔事件
};
总线,管理设备和驱动2条分支
sys/bus/里面device和driver
bus_type结构体
struct bus_type {
const char *name; //总线类型名
struct bus_attribute *bus_attrs; //总线属性和导出到sysfs中的方法
struct device_attribute *dev_attrs; //设备属性和导出到sysfs中的方法
struct driver_attribute *drv_attrs; //驱动程序属性和导出到sysfs中的方法
//匹配函数,检验参数2中的驱动是否支持参数1中的设备
int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev); //探测设备
int (*remove)(struct device *dev); //移除设备
void (*shutdown)(struct device *dev); //关闭函数
int (*suspend)(struct device *dev, pm_message_t state);//改变设备供电状态,使其节能
int (*resume)(struct device *dev); //恢复供电状态,使其正常工作
const struct dev_pm_ops *pm; //关于电源管理的操作符
struct bus_type_private *p; //总线的私有数据
};
关键是 match
函数和 uevent
函数
platform
总线以及pci
总线struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.pm = &platform_dev_pm_ops,
};
struct bus_type pci_bus_type = {
.name = "pci",
.match = pci_bus_match,
.uevent = pci_uevent,
.probe = pci_device_probe,
.remove = pci_device_remove,
.shutdown = pci_device_shutdown,
.dev_groups = pci_dev_groups,
.bus_groups = pci_bus_groups,
.drv_groups = pci_drv_groups,
.pm = PCI_PM_OPS_PTR,
};
struct device 是硬件设备在内核驱动框架中的抽象
device_register 用于向内核驱动框架注册一个设备
通常device不会单独使用,而是被包含在一个具体设备结构体中,如 struct usb_device
是一个基类,会被其他类所继承。
设备驱动模型中的总线式设计中会包含设备和驱动两个
platform总线:platform_device
和 platform_driver
struct device {
struct klist_klist children;/*连接子设备的链表*/
struct device *parent;/*指向父设备的指针*/
struct kobject kobj;/*内嵌的kobject结构体*/
char bus_id[BUS ID SIZE];/*连接到总线上的位置*/
unsigned uevent suppress:1;/*是否支持热插拔事件*/
const char init_name;/*设备的初始化名字*/
struct device_type *type;/*设备相关的特殊处理函数*/
struct bus_type *bus;/*指向连接的总线指针*/
struct device_driver *driver;/*指向该设备的驱动程序*/
void *driver data;/*指向驱动程序私有数据的指针*/
struct dev_pm info power;/*电源管理信息*/
dev t deyt;/*设备号*/
struct class *class;/*指向设备所属类*/
struct attribute_group **groups;/*设备的组属性*/
void (*release) (struct device *dev);/*释放设备描述符的回调函数*/
}
设备属性
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,
char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count);
};
struct device_driver 是驱动程序在内核驱动框架中的抽象
struct device_driver {
const char *name;//设备驱动程序的名字
struct bus_type *bus;//指向驱动属于的总线
struct module *owner;//设备驱动自身模块
const char *mod_name; /* used for built-in modules */
bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
#if defined(CONFIG_OF)
const struct of_device_id *of_match_table;
#endif
int (*probe) (struct device *dev);//探测设备的方法,并检测设备驱动可以控制哪些设备
int (*remove) (struct device *dev);//移除设备调用的方法
void (*shutdown) (struct device *dev);//关闭设备的方法
int (*suspend) (struct device *dev, pm_message_t state);//设备处于低功耗的方法
int (*resume) (struct device *dev);//恢复正常的方法
const struct attribute_group **groups;//属性组
const struct dev_pm_ops *pm;//电源管理
struct driver_private *p;//设备驱动私有数据
};
name
,驱动程序的名字,很重要,经常被用来作为驱动和设备的匹配依据
probe
,驱动程序的探测函数,用来检测一个设备是否可以被该驱动所管理
另一种管理设备和驱动的方式,和总线一样
一个是/class下
一个是/bus下
管理的文件是同一个
相关结构体:struct class
和 struct class_device
udev
的使用离不开class
struct class {
const char *name; //类名称
struct module *owner;
struct class_attribute *class_attrs; //class给自己添加的属性
const struct attribute_group **dev_groups; //class给所包含的设备添加的属性
struct kobject *dev_kobj;
int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
char *(*devnode)(struct device *dev, umode_t *mode);
void (*class_release)(struct class *class);
void (*dev_release)(struct device *dev);
int (*suspend)(struct device *dev, pm_message_t state); //设备休眠时调用
int (*resume)(struct device *dev);
const struct kobj_ns_type_operations *ns_type;
const void *(*namespace)(struct device *dev);
const struct dev_pm_ops *pm; //电源管理
struct subsys_private *p;
};
class的真正意义在于作为同属于一个class的多个设备的容器
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define RAMBLOCK_SIZE (1024*1024) // 1MB,2048扇区
static struct gendisk *my_ramblock_disk; // 磁盘设备的结构体
static struct request_queue *my_ramblock_queue; // 等待队列
static DEFINE_SPINLOCK(my_ramblock_lock);
static int major;
static unsigned char *my_ramblock_buf; // 虚拟块设备的内存指针
static void do_my_ramblock_request(struct request_queue *q)
{
struct request *req;
static int r_cnt = 0; //实验用,打印出驱动读与写的调度方法
static int w_cnt = 0;
req = blk_fetch_request(q);
while (NULL != req)
{
unsigned long start = blk_rq_pos(req) *512;
unsigned long len = blk_rq_cur_bytes(req);
if(rq_data_dir(req) == READ)
{
// 读请求
memcpy(req->buffer, my_ramblock_buf + start, len); //读操作,
printk("do_my_ramblock-request read %d times\n", r_cnt++);
}
else
{
// 写请求
memcpy( my_ramblock_buf+start, req->buffer, len); //写操作
printk("do_my_ramblock request write %d times\n", w_cnt++);
}
if(!__blk_end_request_cur(req, 0))
{
req = blk_fetch_request(q);
}
}
}
static int blk_ioctl(struct block_device *dev, fmode_t no, unsigned cmd, unsigned long arg)
{
return -ENOTTY;
}
static int blk_open (struct block_device *dev , fmode_t no)
{
printk("11111blk mount succeed\n");
return 0;
}
static int blk_release(struct gendisk *gd , fmode_t no)
{
printk("11111blk umount succeed\n");
return 0;
}
static const struct block_device_operations my_ramblock_fops =
{
.owner = THIS_MODULE,
.open = blk_open,
.release = blk_release,
.ioctl = blk_ioctl,
};
static int my_ramblock_init(void)
{
major = register_blkdev(0, "my_ramblock");
if (major < 0)
{
printk("fail to regiser my_ramblock\n");
return -EBUSY;
}
// 实例化
my_ramblock_disk = alloc_disk(1); //次设备个数 ,分区个数 +1
//分配设置请求队列,提供读写能力
my_ramblock_queue = blk_init_queue(do_my_ramblock_request, &my_ramblock_lock);
//设置硬盘属性
my_ramblock_disk->major = major;
my_ramblock_disk->first_minor = 0;
my_ramblock_disk->fops = &my_ramblock_fops;
sprintf(my_ramblock_disk->disk_name, "my_ramblcok"); // /dev/name
my_ramblock_disk->queue = my_ramblock_queue;
set_capacity(my_ramblock_disk, RAMBLOCK_SIZE / 512);
/* 硬件相关操作 */
my_ramblock_buf = kzalloc(RAMBLOCK_SIZE, GFP_KERNEL);
add_disk(my_ramblock_disk); // 向驱动框架注册一个disk或者一个partation的接口
return 0;
}
static void my_ramblock_exit(void)
{
unregister_blkdev(major, "my_ramblock");
del_gendisk(my_ramblock_disk);
put_disk(my_ramblock_disk);
blk_cleanup_queue(my_ramblock_queue);
kfree(my_ramblock_buf);
}
module_init(my_ramblock_init);
module_exit(my_ramblock_exit);
MODULE_LICENSE("GPL");