Linux —— 驱动——platform平台总线

news2024/11/16 15:49:59

platform平台总线是Linux内核中一个重要的概念,主要用于管理那些不通过传统物理总线(如USB、I2C、SPI等)连接的设备,特别是SoC(System on Chip,片上系统)内部集成的外设。以下是对platform平台总线的详细介绍:

一、基本概念

定义:platform平台总线是Linux内核为了保持设备驱动的统一性而虚拟出来的一条总线。它并非物理上存在,而是一种软件抽象,用于连接设备与驱动。
目的:解决嵌入式系统中,部分设备(如SoC内部集成的外设)不依附于传统物理总线的问题,使得所有设备都能通过统一的机制进行管理和注册。

二、主要组成部分

platform_device:表示platform总线下的设备。每个platform_device都包含设备的名称、ID、资源(如内存地址、中断号等)以及设备特定的数据。
platform_driver:表示驱动程序,用于与platform_device进行匹配并控制设备。每个platform_driver都包含探测(probe)函数、移除(remove)函数等,用于设备的初始化和卸载。

三、工作流程

(1)系统启动时注册platform总线:在系统启动过程中,Linux内核会在bus系统中注册platform总线,为其后续的设备和驱动管理提供基础。
bus_type结构体:

(2)注册platform_device:内核移植人员负责将SoC内部集成的外设注册为platform_device,包括设备的名称、ID、资源等信息。

  • name:设备的名称,用于与驱动程序进行匹配。
  • id:设备的ID号,用于区分相同名称下的不同设备实例。匹配名字,-1时不匹配id_entryid_entry
  • dev:一个嵌套的device结构体,包含了设备的通用设备信息,如设备在设备模型中的抽象表示。
  • num_resources 和 resource:分别表示设备资源的数量和指向资源描述符数组的指针,这些资源包括设备的物理地址、中断号等。

  • start:资源的起始地址或编号。对于内存资源,这通常是内存的起始物理地址;对于I/O端口资源,这是端口的起始编号;对于中断资源,这是中断号。
  • end:资源的结束地址或编号。这定义了资源范围的上限,确保驱动程序不会超出设备所占用的资源范围。
  • name:资源的名称。这是一个字符串,用于标识资源,但通常不直接用于驱动程序的匹配或资源请求。
  • flags:资源的类型和属性。这个字段是一个位掩码,用于指示资源的具体类型和可能的其他属性。Linux内核定义了多个宏来表示不同类型的资源,如IORESOURCE_MEM(内存资源)、IORESOURCE_IO(I/O端口资源)、IORESOURCE_IRQ(中断资源)等。此外,还可以设置其他标志位来表示资源的特定属性,如IORESOURCE_PREFETCH(可预取的内存资源)等。

  • id_entry:用于匹配设备和驱动程序的ID结构体指针。
  • pdriver:指向设备对应的平台驱动程序的指针。


(3)注册platform_driver:设备驱动开发人员负责编写并注册platform_driver,包括探测函数、移除函数等。

  • name:驱动程序的名称,用于与 platform_device 进行匹配。
  • probe:设备匹配成功时调用的函数,用于初始化设备。
  • remove:设备卸载时调用的函数,用于清理设备资源。
  • suspend 和 resume:设备进入休眠和唤醒时调用的函数(可选)。
  • driver:一个嵌套的 device_driver 结构体,包含了驱动程序的通用信息。
  • id_table:一个指向 platform_device_id 结构体数组的指针,用于基于ID的匹配(可选)。


(4)匹配与初始化:platform总线的匹配函数会检查已注册的platform_device和platform_driver,当发现匹配的设备和驱动时,会调用驱动的probe函数进行初始化,使设备进入工作状态。

四、优势与特点

统一管理:通过platform总线,Linux内核能够对所有设备(包括传统物理总线设备和SoC内部外设)进行统一管理,提高了系统的整体性和一致性。
提高代码可移植性:platform总线使得设备与驱动的注册和匹配过程更加标准化,降低了代码对特定硬件的依赖,提高了代码的可移植性。
简化驱动开发:对于SoC内部集成的外设,开发者无需关注复杂的总线通信协议,只需通过platform总线即可实现设备与驱动的连接和控制。

五、应用实例

在嵌入式系统开发中,platform总线被广泛应用于SoC内部外设的管理和驱动开发中。例如,LED灯、看门狗定时器、串口控制器等都可以作为platform_device进行注册和管理,并通过相应的platform_driver进行控制。
综上所述,platform平台总线是Linux内核中一个重要的虚拟总线机制,它通过软件抽象的方式解决了嵌入式系统中设备管理和驱动开发的复杂性问题,为开发者提供了更加统一、简化和高效的开发环境。

下面的代码是plat_form总线控制ADC驱动的示例;

adc_driver.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/string.h>
#include <asm/uaccess.h>
#include <linux/miscdevice.h>
#include <asm-generic/errno-base.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/ioctl.h>
#include <linux/platform_device.h>

#define DEV_NAME "adc"
static volatile unsigned long * adccon;
static volatile unsigned long * adcdat0;
static volatile unsigned long * clkcon;
static wait_queue_head_t wq;
static int condition = 0;
static int arg = 100;

#define ADC_MAGIC_NUM 'x'
#define ADC_SET_CHANNEL 2
#define CMD_ADC_SET_CHANNEL _IOW(ADC_MAGIC_NUM, ADC_SET_CHANNEL, unsigned char)

irqreturn_t irq_handler(int num, void * arg)
{
	printk("num = %d  arg = %d\n", num, *(int *)arg);
	condition = 1;
	wake_up_interruptible(&wq);
	return IRQ_HANDLED;
}

static void init_adc(void)
{
	*adccon = (1 << 14) | (49 << 6);
}

static void start_adc(void)
{
	*adccon |= (1 << 0);	
}

static unsigned short read_adc(void)
{
	unsigned short data = *adcdat0 & 0x3ff;
	return data;
}

static int set_channel(unsigned char channel)
{
	if(channel < 0 || channel > 7)
		return -EINVAL;

	*adccon &= ~(0x7 << 3);
	*adccon |= (channel <<3);

	return 0;
}

static int open (struct inode * inode, struct file * file)
{
	init_adc();
	printk("adc open ...\n");
	return 0;
}

static ssize_t read (struct file * file, char __user * buf, size_t len, loff_t * offset)
{
	//copy_to_user(buf, &value, sizeof(value));
	unsigned short value = 0;
	printk("adc read start ...\n");
	condition = 0;
	start_adc();	
	wait_event_interruptible(wq, condition);

	value = read_adc();
	copy_to_user(buf, &value, sizeof(value));
	printk("adc read ...\n");

	return sizeof(value);
}

static ssize_t write (struct file * file, const char __user * buf, size_t len, loff_t * offset)
{
	return 0;
}

static long ioctl(struct file * file, unsigned int cmd, unsigned long arg)
{
	int ret = 0;
	unsigned char args = 0;

	switch(cmd)
	{
	case CMD_ADC_SET_CHANNEL:
		copy_from_user(&args, (unsigned char *)arg, _IOC_SIZE(CMD_ADC_SET_CHANNEL));
		ret = set_channel(args);
		break;
	default :
		ret = -EINVAL;
	}

	return ret;
}

static int close (struct inode * inode, struct file * file)
{
	printk("adc close ...\n");
	return 0;
}

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = open,
	.read = read,
	.write = write,
	.unlocked_ioctl = ioctl,
	.release = close
};

static struct miscdevice misc = 
{
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEV_NAME,
	.fops = &fops
};

static int probe(struct platform_device * pdev)
{
	int ret = misc_register(&misc);
	if(ret < 0)
		goto err_misc_register;

	ret = request_irq(pdev->resource[3].start, irq_handler, IRQF_DISABLED, "adc_irq", &arg);	
	if(ret < 0)
		goto err_request_irq;

	adccon = ioremap(pdev->resource[0].start, pdev->resource[0].end - pdev->resource[0].start + 1);
	adcdat0 = ioremap(pdev->resource[1].start, pdev->resource[1].end - pdev->resource[1].start + 1);
	clkcon = ioremap(pdev->resource[2].start, pdev->resource[2].end - pdev->resource[2].start + 1);
	*clkcon |= (1 << 15);

	init_waitqueue_head(&wq);

	printk("adc_probe  ...\n");
	return ret;

err_misc_register:
	misc_deregister(&misc);
	printk("adc misc_register failed\n");	
	return ret;

err_request_irq:
	disable_irq(pdev->resource[3].start);
	free_irq(pdev->resource[3].start, &arg);
	return ret;
}

static int remove(struct platform_device * pdev)
{
	iounmap(clkcon);
	iounmap(adcdat0);
	iounmap(adccon);
	disable_irq(pdev->resource[3].start);
	free_irq(pdev->resource[3].start, &arg);
	misc_deregister(&misc);
	printk("adc_remove  ...\n");

	return 0;
}

static struct platform_driver dri = 
{
	.probe = probe,
	.remove = remove,
	.driver = 
	{
		.name = DEV_NAME	
	}
};

static int __init adc_driver_init(void)
{
	int ret = platform_driver_register(&dri);
	if(ret < 0)
		goto err_platform_register;
	printk("adc platform_driver_register ...\n");
	return 0;

err_platform_register:
	platform_driver_unregister(&dri);
	printk("adc platform_driver_register failed\n");
	return ret;
}

static void __exit adc_driver_exit(void)
{
	platform_driver_unregister(&dri);
	printk("adc platform_driver_unregister ...\n");
}

module_init(adc_driver_init);
module_exit(adc_driver_exit);
MODULE_LICENSE("GPL");

1、 全局变量定义

  • adcconadcdat0clkcon:这些是指向硬件寄存器的指针,用于控制ADC和时钟。
  • wq:等待队列头,用于在ADC转换完成时唤醒等待的线程。
  • condition:一个标志,用于指示ADC转换是否完成。
  • arg:传递给中断处理程序的参数(在这个例子中,实际上并未在中断处理函数中使用)。

2、设备控制命令

定义了ADC设备的魔法号和ioctl命令,用于设置ADC通道。

3、 中断处理函数

irq_handler函数是一个典型的中断处理函数,它设置condition标志并唤醒等待队列中的线程。

4. ADC操作函数

  • init_adc:初始化ADC控制器。
  • start_adc:启动ADC转换。
  • read_adc:从ADC数据寄存器读取转换结果。
  • set_channel:设置ADC的输入通道。

5. 文件操作函数

  • open:打开设备时调用,初始化ADC。
  • read:从设备读取数据,启动ADC转换,等待转换完成,然后读取结果并返回给用户空间。
  • write:本例中未实现,因为ADC通常不需要写入操作。
  • ioctl:处理设备特定的命令,如设置ADC通道。
  • close:关闭设备时调用,进行清理工作(但在这个例子中,没有特别的清理工作)。

6. 杂项设备注册

使用misc_register函数注册一个杂项设备,这样用户空间就可以通过/dev/adc(或类似的设备文件)来访问ADC设备。

7. 平台设备驱动

  • probe函数:在平台设备被发现时调用,进行资源映射、中断请求等初始化工作。
  • remove函数:在平台设备被移除时调用,进行资源释放等清理工作。
  • platform_driver_registerplatform_driver_unregister用于注册和注销平台设备驱动。

8. 模块初始化和退出

使用module_initmodule_exit宏定义模块的初始化和退出函数,分别用于注册和注销平台设备驱动

adc_diverce.c

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm-generic/errno-base.h>
#include <linux/platform_device.h>
#include <mach/irqs.h>

#define DEV_NAME "adc"
#define ADCCON 0x58000000
#define ADCDAT0 0x5800000C
#define CLKCON 0x4C00000C
#define IRQ_NUM IRQ_ADC

static struct resource res[] = 
{
	[0] = 
	{
		.start = ADCCON,
		.end = ADCCON + 4 - 1,
		.name = "adccon",
		.flags = IORESOURCE_IO
	},
	[1] = 
	{
		.start = ADCDAT0,
		.end = ADCDAT0 + 4 - 1,
		.name = "adcdat0",
		.flags = IORESOURCE_IO
	},
	[2] = 
	{
		.start = CLKCON,
		.end = CLKCON + 4 - 1,
		.name = "clkcon",
		.flags = IORESOURCE_IO
	},
	[3] = 
	{
		.start = IRQ_NUM,
		.end = IRQ_NUM,
		.name = "irq_adc",
		.flags = IORESOURCE_IRQ
	}
};

static void	release(struct device *dev){}

static struct platform_device dev = 
{
	.name = DEV_NAME,
	.id = -1,
	.dev = 
	{
		.release = release	
	},
	.num_resources = sizeof(res) / sizeof(res[0]),
	.resource = res
};

static int __init adc_device_init(void)
{
	int ret = platform_device_register(&dev);
	if(ret < 0)
		goto err_platform_register;

	printk("platform_device_register ...\n");
	return 0;

err_platform_register:
	platform_device_unregister(&dev);
	printk("platform_device_register failed ...\n");
	return ret;
}

static void __exit adc_device_exit(void)
{
	platform_device_unregister(&dev);
	printk("platform_device_unregister  ...\n");
}

module_init(adc_device_init);
module_exit(adc_device_exit);
MODULE_LICENSE("GPL");

主要组成部分

  1. 宏定义
    • DEV_NAME:设备的名称,这里是"adc"。
    • ADCCONADCDAT0CLKCON:ADC控制器、ADC数据寄存器、时钟控制寄存器的内存映射IO地址。
    • IRQ_NUM:ADC中断的编号,这里假设有一个IRQ_ADC宏定义在<mach/irqs.h>中。
  2. 资源定义res数组):
    • 定义了ADC控制器、ADC数据寄存器、时钟控制寄存器和ADC中断的资源。每个资源都有一个起始地址(.start)和结束地址(.end),名称(.name),以及类型(.flags)。对于内存映射IO,使用IORESOURCE_IO;对于中断,使用IORESOURCE_IRQ
  3. 平台设备结构体dev):
    • 包含了设备的名称("adc")、ID(-1表示此设备类型只有一个实例)、释放函数(release,这里为空实现)、资源数量(通过sizeof(res) / sizeof(res[0])计算)和资源列表(res)。
  4. 模块初始化/退出函数
    • adc_device_init:使用platform_device_register函数注册平台设备。如果注册失败,则通过goto语句跳转到错误处理代码,并尝试注销设备。
    • adc_device_exit:使用platform_device_unregister函数注销平台设备。
  5. 模块许可
    • 通过MODULE_LICENSE("GPL")声明该模块使用GPL(GNU通用公共许可证)发布。

注意事项

  • 这段代码假设了IRQ_ADC和相关的内存映射地址在<mach/irqs.h>或其他包含文件中已经定义。这通常取决于具体的硬件平台和内核配置。
  • 释放函数release在这里是空的,但在实际应用中,如果设备分配了任何资源(如内存、中断号等),则应该在这里进行清理。
  • 该模块本身不直接处理ADC数据的读取或中断服务例程的编写,这些功能通常需要由与这个设备关联的设备驱动程序来实现。
  • 在实际部署中,还需要考虑内核配置(如CONFIG_PLATFORM_DRIVER等)和可能的依赖关系(如特定的硬件抽象层或板级支持包)。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2085932.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mysql8.0查询等级排名可使用窗口函数,那5.7的版本呢?

1、需求&#xff1a;查询用户详情的同时查询用户的排名 2、首先看下数据库表设计 分为会员用户表member_user和会员等级表member_level&#xff0c;升级的条件是根据经验值升级&#xff0c;表结构如下&#xff1a; 用户表 member_user CREATE TABLE member_user (id bigint(…

C#操作ms office实现office转pdf

前提 安装office 2019 安装vs 2022 新建项目 引入4个com包 编写代码 代码结构 代码如下 using Microsoft.Office.Interop.Excel;namespace UseMsOffice {internal class Program{static void Main(string[] args){WordToPdf();ExcelToPdf();PPTToPdf();}static void W…

SpringBoot3与AOP完美结合:轻松追踪用户操作,实现精准日志记录

程序员必备宝典https://tmxkj.top/#/ 1.pom文件 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><dependency><groupId>com.alibaba</groupId&g…

LlamaIndex 实现 Agent

RAG 是在数据层面为大模型提供更多、更新的外部知识&#xff0c;而 Agent &#xff08;智能体&#xff09;&#xff0c;为大模型扩展了推理业务的能力。数据是静态的&#xff0c;数据周期可能是天、小时甚至到秒&#xff0c;通过 RAG 实现时&#xff0c;需要调用对应系统的 API…

uni-app组件

一. 什么是组件,有什么好处? 在uni-app中&#xff0c;组件是构成应用的基本单位&#xff0c;它们是用来定义用户界面的一部分&#xff0c;并且通常包含了视图和逻辑。组件的设计使得开发者能够以声明式的方式构建应用界面&#xff0c;并且通过组件化的开发方式来提高代码的复…

vue-cli搭建项目过程

一.前言 传统的前端项目架构&#xff1a; 指的就是一个项目中有很多个HTML文件&#xff0c;每一个HTML文件都是相互独立的&#xff0c;如果需要在页面中导入一些外部依赖的css,js文件&#xff0c;就需要在每一个html文件中都导入就会显得特别麻烦&#xff0c;而且这些外部依赖…

详细git使用教程以及git base here命令行

0 下载 这个是官网下载特别慢 Git - Downloads (git-scm.com) 1 最基本操作与初始配置 1.1&#xff0c;linux的基本命令可用 下载安装后鼠标右键选git base here即可打开 1.2&#xff0c;git init /git clone初始化&#xff0c;创建本地仓库 出现.git隐藏文件 git clone “…

Big Model Weekly | 第34期

点击蓝字 关注我们 AI TIME欢迎每一位AI爱好者的加入&#xff01; 01 MiniCPM-V: A GPT-4V Level MLLM on Your Phone 近期多模态大型语言模型&#xff08;MLLMs&#xff09;的快速发展&#xff0c;从根本上改变了人工智能研究和产业的格局&#xff0c;为实现人工智能的下一个重…

Java去掉字符串中的特殊符号只保留中文数字和字母

今天在做一个导入功能发现用户导入的数据有特殊符号&#xff0c;于是想着给他去掉&#xff0c;搜了一下发现大多数方法都只保留了字母数字&#xff0c;连中文都去掉了&#xff0c;这很明显不符合我的需求 直接上代码 /*** author Sakura* date 2024/8/27 15:18*/ public clas…

Python(C++)自动微分导图

&#x1f3af;要点 反向传播矢量化计算方式前向传递和后向传递计算方式图节点拓扑排序一阶二阶前向和伴随模式计算二元分类中生成系数高斯噪声和特征二元二次方程有向无环计算图超平面搜索前向梯度下降算法快速傅里叶变换材料应力和切线算子GPU CUDA 神经网络算术微分 Pytho…

使用谷歌浏览器查看原型

需求人员给了一个原型文件包&#xff0c;用谷歌浏览器打开提示以下内容&#xff1a; 找到需求人员发的原型文件包 进入到resources-->chrome&#xff0c;找到axure-chrome-extension.crx&#xff0c;复制一份出来命名为axure-chrome-extension.tar&#xff0c;然后在该目录下…

招联金融基于 Apache Doris 数仓升级:单集群 QPS 超 10w,存储成本降低 70%

在竞争激烈的消费金融市场中&#xff0c;有效利用海量数据、提升业务运营效率是赢得市场的关键。早期招联采用典型的 Lambda 架构提供业务报表、数据运营、个性推荐、风险控制等数据服务&#xff0c;而 Lambda 过多的技术栈也引发了数据孤岛、查询效率不足、代码复用性差以及开…

AI算法平台训练站裸土检测算法训练裸土检测算法源码

在全球化进程加快与环境问题日益突出的今天&#xff0c;裸土检测成为了环境监测和土壤管理中不可或缺的一环。裸土指的是没有植被覆盖的土壤区域&#xff0c;这些区域易受侵蚀&#xff0c;并可能导致土壤流失和环境退化。为了有效应对这些问题&#xff0c;裸土检测算法应运而生…

Redis持久化与主从同步

1 淘汰策略 127.0.0.1:6379> help expireEXPIRE key secondssummary: Set a keys time to live in secondssince: 1.0.0group: generic127.0.0.1:6379> help PEXPIREPEXPIRE key millisecondssummary: Set a keys time to live in millisecondssince: 2.6.0group: gener…

【CSP:202112-1】序列查询(Java)

题目链接 202112-1 序列查询 题目描述 求解思路 模拟&#xff1a;a数组可以看作是记录 f ( x ) f(x) f(x) 函数值发生变化出的 x x x 点&#xff08;每次自增1&#xff09;。因此将每段相同数值的 f ( x ) f(x) f(x) 用乘法计算出来即可&#xff0c;最后记得要加上最后一…

Java Web —— 第九天(事务)

事务管理 & AOP 事务回顾 概念 事务 是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;这些操作 要么同时成功&#xff0c;要么同时失败 操作 开启事务(一组操作开始前&#xff0c;开启事务): start transaction / begin 提交事务(这组操作全部成功…

服务器访问端口命令

服务器访问端口命令是一组用于管理服务器端口的命令行指令。服务器端口是用于与外部设备或应用程序进行通信的逻辑通道&#xff0c;它允许数据在服务器和其他设备之间传输。以下是一些常见的服务器访问端口命令。 netstat&#xff1a;这个命令用于检查服务器上当前的网络连接和…

FPGA第 5 篇,FPGA技术优略势,FPGA学习方向,FPGA学习路线(FPGA专业知识的学习方向,FPGA现场可编程门阵列学习路线和方向)

前言 前几篇讲了一下FPGA的发展和应用&#xff0c;以及未来前景。具体详细&#xff0c;请看 FPGA发展和应用&#xff0c;以及未来前景https://blog.csdn.net/weixin_65793170/category_12665249.html 这里我们来&#xff0c;记录一下&#xff0c;FPGA专业知识的学习路线 一.…

OpenAI remove key access while using AAD authentication

题意&#xff1a;“OpenAI 在使用 AAD 认证时移除了密钥访问权限” 问题背景&#xff1a; I am calling Azure OpenAI API in my python code. To set it up, we need to provide a few parameters, one of which is openai.api_key. There are 2 options to get this value -…

力扣hot100-动态规划

文章目录 概念动态规划基本思想常见步骤常用技巧常见问题类型 动态规划题目题目&#xff1a; 爬楼梯题解 概念 动态规划 动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种解决问题的算法思想&#xff0c;通常用于优化问题。它的核心思想是将一个…