platform设备驱动实验

news/2024/12/1 5:33:07/

一、Linux 驱动的分离与分层

1、驱动的分隔与分离

传统驱动编写思路如下图:
在这里插入图片描述

下图这个就是 Linux 中的总线(bus)、驱动(driver)和设备(device)模型,也就是常说的驱动分离。

在这里插入图片描述

在这里插入图片描述

2、驱动的分层

分层的目的也是为了在不同的层处理不同的内容,以input子系统简述驱动分层,input 子系统负责管理所有跟输入有关的驱动,包括键盘、鼠标、触摸等,最底层的就是设备原始驱动,负责获取输入设备的原始值,获取到的输入事件上报给 input 核心层。input 核心层会处理各种 IO 模型,并且提供 file_operations 操作集合。我们在编写输入设备驱动的时候只需要处理好输入事件的上报即可,至于如何处理这些上报的输入事件那是上层去考虑的,我们不用管。

二、platform 平台驱动模型简介

1、platform 总线

Linux系统内核使用bus_type结构体表示总线如下:

struct bus_type {...int (*match)(struct device *dev, struct device_driver *drv);...
};

match 函数,此函数很重要,此函数就是完成设备和驱动之间匹配的,总线就是使用 match 函数来根据注册的设备来查找对应的驱动,或者根据注册的驱动来查找相应的设备,因此每一条总线都必须实现此函数。match 函数有两个参数:dev 和 drv,这两个参数分别为 device 和 device_driver 类型,也就是设备和驱动。

platform 总线定义如下:

struct bus_type platform_bus_type = {.name = "platform",.dev_groups = platform_dev_groups,.match = platform_match,.uevent = platform_uevent,.pm = &platform_dev_pm_ops,
};

platform_bus_type 就是 platform 平台总线,其中 platform_match 就是匹配函数。

static int platform_match(struct device *dev,struct device_driver *drv)
{struct platform_device *pdev = to_platform_device(dev);struct platform_driver *pdrv = to_platform_driver(drv);/*When driver_override is set,only bind to the matching driver*/if (pdev->driver_override)return !strcmp(pdev->driver_override, drv->name);/* Attempt an OF style match first */if (of_driver_match_device(dev, drv))return 1;/* Then try ACPI style match */if (acpi_driver_match_device(dev, drv))return 1;/* 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);
}

2、 platform 驱动

struct platform_driver {int (*probe)(struct platform_device *);	//当驱动与设备匹配成功以后 probe 函数就会执行,非常重要的函数!!需要自己编写。int (*remove)(struct platform_device *);void (*shutdown)(struct platform_device *);int (*suspend)(struct platform_device *, pm_message_t state);int (*resume)(struct platform_device *);struct device_driver driver;const struct platform_device_id *id_table;bool prevent_deferred_probe;
};
struct platform_device_id {char name[PLATFORM_NAME_SIZE];kernel_ulong_t driver_data;
};
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 */const struct of_device_id *of_match_table;const struct acpi_device_id *acpi_match_table;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;
};
struct of_device_id {char name[32];char type[32];char compatible[128];const void *data;
};

当我们定义并初始化好 platform_driver 结构体变量以后,需要在驱动入口函数里面调用platform_driver_register 函数向 Linux 内核注册一个 platform 驱动,platform_driver_register 函数原型如下所示:

int platform_driver_register (struct platform_driver *driver)

函数参数和返回值含义如下:
driver:要注册的 platform 驱动。
返回值:负数,失败;0,成功。
还需要在驱动卸载函数中通过 platform_driver_unregister 函数卸载 platform 驱动,
platform_driver_unregister 函数原型如下:

void platform_driver_unregister(struct platform_driver *drv)

函数参数和返回值含义如下:
drv:要卸载的 platform 驱动。
返回值:无。

platform 驱动框架如下所示

struct xxx_dev{struct cdev cdev;/* 设备结构体其他具体内容 */
};struct xxx_dev xxxdev; /* 定义个设备结构体变量 */static int xxx_open(struct inode *inode, struct file *filp)
{ /* 函数具体内容 */return 0;
}static ssize_t xxx_write(struct file *filp, const char __user *buf,size_t cnt, loff_t *offt)
{/* 函数具体内容 */return 0;
}/*
* 字符设备驱动操作集
*/
static struct file_operations xxx_fops = {.owner = THIS_MODULE,.open = xxx_open,.write = xxx_write,
};/*
* platform 驱动的 probe 函数
* 驱动与设备匹配成功以后此函数就会执行
*/
static int xxx_probe(struct platform_device *dev)
{ ......cdev_init(&xxxdev.cdev, &xxx_fops); /* 注册字符设备驱动 *//* 函数具体内容 */return 0;
}static int xxx_remove(struct platform_device *dev)
{......cdev_del(&xxxdev.cdev);/* 删除 cdev *//* 函数具体内容 */return 0;
}/* 匹配列表 */
static const struct of_device_id xxx_of_match[] = {{ .compatible = "xxx-gpio" },{ /* Sentinel */ }
};/* 
* platform 平台驱动结构体
*/
static struct platform_driver xxx_driver = {.driver = {.name = "xxx",.of_match_table = xxx_of_match,},.probe = xxx_probe,.remove = xxx_remove,
};/* 驱动模块加载 */
static int __init xxxdriver_init(void)
{return platform_driver_register(&xxx_driver);
}/* 驱动模块卸载 */
static void __exit xxxdriver_exit(void)
{platform_driver_unregister(&xxx_driver);
}module_init(xxxdriver_init);
module_exit(xxxdriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsd");

3、platform 设备

struct platform_device {const char *name; 	//比如对应的 platform 驱动的 name 字段为“xxx-gpio”,那么此 name字段也要设置为“xxx-gpio”。int id; bool id_auto;struct device dev;u32 num_resources; struct resource *resource;const struct platform_device_id *id_entry;char *driver_override; /* Driver name to force a match *//* MFD cell pointer */struct mfd_cell *mfd_cell;/* arch specific additions */struct pdev_archdata archdata;
};

resource 表示资源,也就是设备信息,比如外设寄存器等。Linux 内核使用 resource结构体表示资源,resource 结构体内容如下:

struct resource {resource_size_t start;resource_size_t end;const char *name;unsigned long flags;struct resource *parent, *sibling, *child;
};

start 和 end 分别表示资源的起始和终止信息,对于内存类的资源,就表示内存起始和终止地址,name 表示资源名字,flags 表示资源类型,可选的资源类型都定义在了文件include/linux/ioport.h 里面,如下所示:

#define IORESOURCE_BITS 0x000000ff /* Bus-specific bits */#define IORESOURCE_TYPE_BITS 0x00001f00 /* Resource type */
#define IORESOURCE_IO 0x00000100 /* PCI/ISA I/O ports */
#define IORESOURCE_MEM 0x00000200
#define IORESOURCE_REG 0x00000300 /* Register offsets */
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
......
/* PCI control bits. Shares IORESOURCE_BITS with above PCI ROM. */
#define IORESOURCE_PCI_FIXED (1<<4) /* Do not move resource */

在以前不支持设备树的Linux版本中,用户需要编写platform_device变量来描述设备信息,然后使用 platform_device_register 函数将设备信息注册到 Linux 内核中,此函数原型如下所示:

int platform_device_register(struct platform_device *pdev)

函数参数和返回值含义如下:
pdev:要注册的 platform 设备。
返回值:负数,失败;0,成功。
如果不再使用 platform 的话可以通过 platform_device_unregister 函数注销掉相应的 platform
设备,platform_device_unregister 函数原型如下:

void platform_device_unregister(struct platform_device *pdev)

函数参数和返回值含义如下:
pdev:要注销的 platform 设备。
返回值:无。

platform 设备信息框架如下所示:

/* 寄存器地址定义*/
#define PERIPH1_REGISTER_BASE (0X20000000) /* 外设 1 寄存器首地址 */ 
#define PERIPH2_REGISTER_BASE (0X020E0068) /* 外设 2 寄存器首地址 */
#define REGISTER_LENGTH 4/* 资源 */
static struct resource xxx_resources[] = {[0] = {.start = PERIPH1_REGISTER_BASE,.end = (PERIPH1_REGISTER_BASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,}, [1] = {.start = PERIPH2_REGISTER_BASE,.end = (PERIPH2_REGISTER_BASE + REGISTER_LENGTH - 1),.flags = IORESOURCE_MEM,},
};/* platform 设备结构体 */
static struct platform_device xxxdevice = {.name = "xxx-gpio",.id = -1,.num_resources = ARRAY_SIZE(xxx_resources),.resource = xxx_resources,
};/* 设备模块加载 */
static int __init xxxdevice_init(void)
{return platform_device_register(&xxxdevice);
}/* 设备模块注销 */
static void __exit xxx_resourcesdevice_exit(void)
{platform_device_unregister(&xxxdevice);
}module_init(xxxdevice_init);
module_exit(xxxdevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("hsd");

三、试验程序编写(不带设备树)

驱动

#include <linux/types.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/ide.h>#include <linux/init.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/gpio.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/of_gpio.h>#include <linux/semaphore.h>#include <linux/timer.h>#include <linux/irq.h>#include <linux/wait.h>#include <linux/poll.h>#include <linux/fs.h>#include <linux/fcntl.h>#include <linux/platform_device.h>#include <asm/mach/map.h>#include <asm/uaccess.h>#include <asm/io.h>#define LEDDEV_CNT		1			/* 设备号长度 	*/#define LEDDEV_NAME		"platled"	/* 设备名字 	*/#define LEDOFF 			0#define LEDON 			1/* 寄存器名 */static void __iomem *IMX6U_CCM_CCGR1;static void __iomem *SW_MUX_GPIO1_IO03;static void __iomem *SW_PAD_GPIO1_IO03;static void __iomem *GPIO1_DR;static void __iomem *GPIO1_GDIR;/* leddev设备结构体 */struct leddev_dev{dev_t devid;			/* 设备号	*/struct cdev cdev;		/* cdev		*/struct class *class;	/* 类 		*/struct device *device;	/* 设备		*/int major;				/* 主设备号	*/		};struct leddev_dev leddev; 	/* led设备 *//** @description		: LED打开/关闭* @param - sta 	: LEDON(0) 打开LED,LEDOFF(1) 关闭LED* @return 			: 无*/void led0_switch(u8 sta){u32 val = 0;if(sta == LEDON){val = readl(GPIO1_DR);val &= ~(1 << 3);	writel(val, GPIO1_DR);}else if(sta == LEDOFF){val = readl(GPIO1_DR);val|= (1 << 3);	writel(val, GPIO1_DR);}	}/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/static int led_open(struct inode *inode, struct file *filp){filp->private_data = &leddev; /* 设置私有数据  */return 0;}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt){int retvalue;unsigned char databuf[1];unsigned char ledstat;retvalue = copy_from_user(databuf, buf, cnt);if(retvalue < 0) {return -EFAULT;}ledstat = databuf[0];		/* 获取状态值 */if(ledstat == LEDON) {led0_switch(LEDON);		/* 打开LED灯 */}else if(ledstat == LEDOFF) {led0_switch(LEDOFF);	/* 关闭LED灯 */}return 0;}/* 设备操作函数 */static struct file_operations led_fops = {.owner = THIS_MODULE,.open = led_open,.write = led_write,};/** @description		: flatform驱动的probe函数,当驱动与* 					  设备匹配以后此函数就会执行* @param - dev 	: platform设备* @return 			: 0,成功;其他负值,失败*/static int led_probe(struct platform_device *dev){	int i = 0;int ressize[5];u32 val = 0;struct resource *ledsource[5];printk("led driver and device has matched!\r\n");/* 1、获取资源 */for (i = 0; i < 5; i++) {ledsource[i] = platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */if (!ledsource[i]) {dev_err(&dev->dev, "No MEM resource for always on\n");return -ENXIO;}ressize[i] = resource_size(ledsource[i]);	}	/* 2、初始化LED *//* 寄存器地址映射 */IMX6U_CCM_CCGR1 = ioremap(ledsource[0]->start, ressize[0]);SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start, ressize[1]);SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start, ressize[2]);GPIO1_DR = ioremap(ledsource[3]->start, ressize[3]);GPIO1_GDIR = ioremap(ledsource[4]->start, ressize[4]);val = readl(IMX6U_CCM_CCGR1);val &= ~(3 << 26);				/* 清除以前的设置 */val |= (3 << 26);				/* 设置新值 */writel(val, IMX6U_CCM_CCGR1);/* 设置GPIO1_IO03复用功能,将其复用为GPIO1_IO03 */writel(5, SW_MUX_GPIO1_IO03);writel(0x10B0, SW_PAD_GPIO1_IO03);/* 设置GPIO1_IO03为输出功能 */val = readl(GPIO1_GDIR);val &= ~(1 << 3);			/* 清除以前的设置 */val |= (1 << 3);			/* 设置为输出 */writel(val, GPIO1_GDIR);/* 默认关闭LED1 */val = readl(GPIO1_DR);val |= (1 << 3) ;	writel(val, GPIO1_DR);/* 注册字符设备驱动 *//*1、创建设备号 */if (leddev.major) {		/*  定义了设备号 */leddev.devid = MKDEV(leddev.major, 0);register_chrdev_region(leddev.devid, LEDDEV_CNT, LEDDEV_NAME);} else {						/* 没有定义设备号 */alloc_chrdev_region(&leddev.devid, 0, LEDDEV_CNT, LEDDEV_NAME);	/* 申请设备号 */leddev.major = MAJOR(leddev.devid);	/* 获取分配号的主设备号 */}/* 2、初始化cdev */leddev.cdev.owner = THIS_MODULE;cdev_init(&leddev.cdev, &led_fops);/* 3、添加一个cdev */cdev_add(&leddev.cdev, leddev.devid, LEDDEV_CNT);/* 4、创建类 */leddev.class = class_create(THIS_MODULE, LEDDEV_NAME);if (IS_ERR(leddev.class)) {return PTR_ERR(leddev.class);}/* 5、创建设备 */leddev.device = device_create(leddev.class, NULL, leddev.devid, NULL, LEDDEV_NAME);if (IS_ERR(leddev.device)) {return PTR_ERR(leddev.device);}return 0;}/** @description		: platform驱动的remove函数,移除platform驱动的时候此函数会执行* @param - dev 	: platform设备* @return 			: 0,成功;其他负值,失败*/static int led_remove(struct platform_device *dev){iounmap(IMX6U_CCM_CCGR1);iounmap(SW_MUX_GPIO1_IO03);iounmap(SW_PAD_GPIO1_IO03);iounmap(GPIO1_DR);iounmap(GPIO1_GDIR);cdev_del(&leddev.cdev);/*  删除cdev */unregister_chrdev_region(leddev.devid, LEDDEV_CNT); /* 注销设备号 */device_destroy(leddev.class, leddev.devid);class_destroy(leddev.class);return 0;}/* platform驱动结构体 */static struct platform_driver led_driver = {.driver		= {.name	= "imx6ul-led",			/* 驱动名字,用于和设备匹配 */},.probe		= led_probe,.remove		= led_remove,};/** @description	: 驱动模块加载函数* @param 		: 无* @return 		: 无*/static int __init leddriver_init(void){return platform_driver_register(&led_driver);}/** @description	: 驱动模块卸载函数* @param 		: 无* @return 		: 无*/static void __exit leddriver_exit(void){platform_driver_unregister(&led_driver);}module_init(leddriver_init);module_exit(leddriver_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("zuozhongkai");

设备

#include <linux/types.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/ide.h>#include <linux/init.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/gpio.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/of_gpio.h>#include <linux/semaphore.h>#include <linux/timer.h>#include <linux/irq.h>#include <linux/wait.h>#include <linux/poll.h>#include <linux/fs.h>#include <linux/fcntl.h>#include <linux/platform_device.h>#include <asm/mach/map.h>#include <asm/uaccess.h>#include <asm/io.h>/* * 寄存器地址定义*/#define CCM_CCGR1_BASE				(0X020C406C)	#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)#define GPIO1_DR_BASE				(0X0209C000)#define GPIO1_GDIR_BASE				(0X0209C004)#define REGISTER_LENGTH				4/* @description		: 释放flatform设备模块的时候此函数会执行	* @param - dev 	: 要释放的设备 * @return 			: 无*/static void	led_release(struct device *dev){printk("led device released!\r\n");	}/*  * 设备资源信息,也就是LED0所使用的所有寄存器*/static struct resource led_resources[] = {[0] = {.start 	= CCM_CCGR1_BASE,.end 	= (CCM_CCGR1_BASE + REGISTER_LENGTH - 1),.flags 	= IORESOURCE_MEM,},	[1] = {.start	= SW_MUX_GPIO1_IO03_BASE,.end	= (SW_MUX_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),.flags	= IORESOURCE_MEM,},[2] = {.start	= SW_PAD_GPIO1_IO03_BASE,.end	= (SW_PAD_GPIO1_IO03_BASE + REGISTER_LENGTH - 1),.flags	= IORESOURCE_MEM,},[3] = {.start	= GPIO1_DR_BASE,.end	= (GPIO1_DR_BASE + REGISTER_LENGTH - 1),.flags	= IORESOURCE_MEM,},[4] = {.start	= GPIO1_GDIR_BASE,.end	= (GPIO1_GDIR_BASE + REGISTER_LENGTH - 1),.flags	= IORESOURCE_MEM,},};/** platform设备结构体 */static struct platform_device leddevice = {.name = "imx6ul-led",.id = -1,.dev = {.release = &led_release,},.num_resources = ARRAY_SIZE(led_resources),.resource = led_resources,};/** @description	: 设备模块加载 * @param 		: 无* @return 		: 无*/static int __init leddevice_init(void){return platform_device_register(&leddevice);}/** @description	: 设备模块注销* @param 		: 无* @return 		: 无*/static void __exit leddevice_exit(void){platform_device_unregister(&leddevice);}module_init(leddevice_init);module_exit(leddevice_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("hsd");

APP

#include "stdio.h"#include "unistd.h"#include "sys/types.h"#include "sys/stat.h"#include "fcntl.h"#include "stdlib.h"#include "string.h"#define LEDOFF 	0#define LEDON 	1/** @description		: main主程序* @param - argc 	: argv数组元素个数* @param - argv 	: 具体参数* @return 			: 0 成功;其他 失败*/int main(int argc, char *argv[]){int fd, retvalue;char *filename;unsigned char databuf[2];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开led驱动 */fd = open(filename, O_RDWR);if(fd < 0){printf("file %s open failed!\r\n", argv[1]);return -1;}databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 */retvalue = write(fd, databuf, sizeof(databuf));if(retvalue < 0){printf("LED Control Failed!\r\n");close(fd);return -1;}retvalue = close(fd); /* 关闭文件 */if(retvalue < 0){printf("file %s close failed!\r\n", argv[1]);return -1;}return 0;}

http://www.ppmy.cn/news/33590.html

相关文章

【Unity小知识】Editor编写常用方法汇总

汇总一些Unity Editor开发的常用方法和实现方式&#xff0c;会持续更新。 添加自定义菜单栏方法 using UnityEngine; using UnityEditor;public class EditorTools : EditorWindow {[MenuItem("EditorTools/自定义的编辑器方法")]public static void CustomEditroFu…

WPF 认识WPF

什么是WPF?WPF是Windows Presentation Foundation(Windows展示基础)简称&#xff0c;顾名思义是专门编写表示层的技术。WPF绚丽界面如下&#xff1a;GUI发展及WPF历史&#xff1f;Windows系统平台上从事图形用户界面GUI(Graphic User Interface)已经经历了多次换代&#xff0c…

《C++ Primer Plus》(第6版)第13章编程练习

《C Primer Plus》&#xff08;第6版&#xff09;第13章编程练习《C Primer Plus》&#xff08;第6版&#xff09;第13章编程练习1. Cd类2. 使用动态内存分配重做练习13. baseDMA、lacksDMA、hasDMA类4. Port类和VintagePort类《C Primer Plus》&#xff08;第6版&#xff09;第…

基于STM32的ADC采样及各式滤波实现(HAL库,含VOFA+教程)

前言&#xff1a;本文为手把手教学ADC采样及各式滤波算法的教程&#xff0c;本教程的MCU采用STM32F103ZET6。以HAL库的ADC采样函数为基础进行教学&#xff0c;通过各式常见滤波的实验结果进行分析对比&#xff0c;搭配VOFA工具直观的展示滤波效果。ADC与滤波算法都是嵌入式较为…

应用服务漏洞扫描与利用

任务环境说明: 服务器场景:Server15服务器场景操作系统:未知(关闭链接)使用命令nmap探测目标靶机的服务版本信息,将需要使用的参数作为FLAG进行提交;FLAG:sV 通过上述端口访问靶机系统并探测隐藏的页面,将找到的敏感文件、目录名作为FLAG(形式:[敏感文件或目录1,敏感…

不要迷信 QUIC

很多人都在强调 QUIC 能解决 HoL blocking 问题&#xff0c;不好意思&#xff0c;我又要泼冷水了。假设大家都懂 QUIC&#xff0c;不再介绍 QUIC 的细节&#xff0c;直接说问题。 和 TCP 一样&#xff0c;QUIC 也是一个基于连接的&#xff0c;保序的可靠传输协议&#xff0c;T…

redis 存储一个map 怎么让map中其中一个值设置过期时间,而不是过期掉整个map?

文章目录 redis 存储一个map 怎么让map中其中一个值设置过期时间,而不是过期掉整个map?Java 中 怎么 实现?方案一: Jedis方案二: Lettuce方案三: Redisson方案四: Jedisson方案五: RedisTemplate那种方式 效率最高 ?拓展:结语redis 存储一个map 怎么让map中其中一个值设置过…

Java面试总结篇

引用介绍 1.线程安全不安全的概念 ​ 线程安全: 指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。 ​ 线程不安全: 是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏…