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

bus_type

一、主要作用

  1. 设备管理

    • bus_type负责管理连接在特定总线上的设备。它维护一个设备链表,其中包含了所有注册到该总线上的设备。通过这个链表,内核可以方便地遍历和管理连接在该总线上的设备。
    • 例如,对于 PCI 总线,bus_type会管理所有连接在 PCI 总线上的设备,包括显卡、网卡、声卡等。
  2. 驱动匹配

    • bus_type在设备和驱动程序之间进行匹配。当一个设备注册到总线上时,bus_type会尝试将其与注册在该总线上的驱动程序进行匹配。如果找到了匹配的驱动程序,就会调用驱动程序的探测函数来初始化设备。
    • 例如,当一个新的 USB 设备插入系统时,USB 总线的bus_type会查找已注册的 USB 驱动程序,看是否有能够支持该设备的驱动。如果找到了匹配的驱动,就会调用驱动程序的探测函数来初始化设备,使设备能够正常工作。

二、结构组成

  1. 名称和属性

    • bus_type包含了总线的名称和一些属性标志。总线的名称用于标识不同类型的总线,例如 "pci" 表示 PCI 总线,"usb" 表示 USB 总线等。属性标志可以表示总线的一些特性,例如是否支持热插拔、是否支持电源管理等。
  2. 设备和驱动注册函数

    • bus_type提供了设备和驱动注册的函数指针。这些函数用于将设备和驱动程序注册到总线上。例如,bus_register函数用于注册总线,device_register函数用于注册设备,driver_register函数用于注册驱动程序。
  3. 匹配函数

    • bus_type包含了一个匹配函数指针,用于在设备和驱动程序之间进行匹配。这个匹配函数通常会比较设备和驱动程序的一些属性,例如设备的 ID、驱动程序支持的设备类型等,以确定它们是否匹配。

三、使用示例

以 PCI 总线为例,当一个 PCI 设备插入系统时,内核会调用 PCI 总线的bus_type中的设备注册函数将设备注册到 PCI 总线上。然后,内核会遍历已注册的 PCI 驱动程序,通过bus_type中的匹配函数来查找能够支持该设备的驱动程序。如果找到了匹配的驱动程序,就会调用驱动程序的探测函数来初始化设备。

bus_type与platform的关系

两者都是 Linux 内核设备驱动模型的重要组成部分,目的都是实现设备与驱动程序的有效管理和匹配,使得硬件设备能够正常工作。

bus_type主要针对传统的硬件总线,管理那些遵循特定总线协议的设备。例如 PCI 总线上的设备通常有明确的总线规范和配置方式。

platform则适用于一些不直接依附于传统硬件总线的设备,如一些定制的硬件模块或嵌入式系统中的特定设备。

platform平台总线

一、平台总线的作用

平台总线是一种虚拟的总线,用于连接那些不直接依附于传统硬件总线(如 PCI、USB 等)的设备。它为这些设备提供了一种统一的管理和交互方式,使得设备驱动程序能够以一种相对独立的方式进行开发和注册。

二、平台设备(platform_device)

  1. 定义与属性

    • 平台设备通常用**struct platform_device**结构体来描述。这个结构体包含了设备的名称、设备 ID、所使用的硬件资源(如内存地址范围、中断号等)以及设备的释放函数等信息。
    • 例如,设备的名称用于与驱动程序进行匹配,当驱动程序的名称与设备名称相同时,内核会尝试进行设备与驱动的匹配操作。
  2. 注册与管理

    • 平台设备可以通过**platform_device_register**函数注册到内核中,此时内核会将其添加到平台总线管理的设备链表中。
    • 一旦注册成功,内核就可以根据设备的属性进行资源分配和管理,并在合适的时候与相应的驱动程序进行匹配。

三、平台驱动(platform_driver)

  1. 定义与属性

    • 平台驱动用**struct platform_driver**结构体来描述。它包含了驱动程序的探测函数(probe)、移除函数(remove)以及驱动程序的名称等属性。
    • 探测函数在设备与驱动程序匹配成功后被调用,用于初始化设备并建立设备与驱动程序之间的关联。移除函数则在设备从系统中移除时被调用,用于执行清理操作。
  2. 注册与匹配

    • 平台驱动可以通过**platform_driver_register**函数注册到内核中,此时内核会将其添加到平台总线管理的驱动链表中。
    • 当内核在设备链表和驱动链表中进行查找时,如果发现一个设备的名称与一个驱动程序的名称相同,就会尝试进行匹配。如果匹配成功,内核会调用驱动程序的探测函数来进一步确认设备的兼容性,并进行设备的初始化操作。

四、应用场景

平台总线和相关的设备驱动架构在嵌入式系统和各种特定硬件设备的驱动开发中非常常见。例如,在嵌入式系统中,一些定制的硬件设备可能没有标准的硬件总线接口,这时就可以使用平台总线来实现设备的驱动和管理。此外,一些简单的外设设备,如 GPIO、UART 等,也可以通过平台总线进行驱动开发,提高代码的可移植性和可维护性。

完整代码

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

adc_device.c

cpp 复制代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/module.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/platform_device.h>

//device里只保留设备资源
#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,  //资源的结束地址,表示从0x58000000到0x58000003,共四个字节
		.name = "adccon",		//资源的名称
		.flags = IORESOURCE_IO  //设备的类型和属性,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)
{
	//关闭设备硬件资源或取消注册中断
	printk("adc_device release ... \n");
}

//该结构体表示平台设备,描述硬件设备基本信息
static struct platform_device dev = 
{
	.name = DEV_NAME,		//设备名,用于与驱动程序进行匹配
	.id = -1,				//设备id,区分相同名称下的不同设备,-1为默认值,表示通用的,不需要特定的id来区分
	.dev =					//struct device 包含设备基本属性和操作
	{
		.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_device_register;

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

err_platform_device_register:
	printk("platform_device_register faied\n");
	platform_device_unregister(&dev);
	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");

adc_driver.c

cpp 复制代码
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/types.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 <linux/interrupt.h>
#include <linux/irqreturn.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/platform_device.h>

//driver里把描述资源的去掉
#define DEV_NAME "adc"
static wait_queue_head_t wq;
static int condition = 0;

static volatile unsigned long* adccon;
static volatile unsigned long* adcdat0;
static volatile unsigned long* clkcon;

#define ADC_MAGIC_NUM 'x'	//幻数:代表不同的设备
#define SET_CHANNEL 1		//命令编号,这里表示要设置通道
#define CMD_SET_CHENNEL _IOW(ADC_MAGIC_NUM, SET_CHANNEL, unsigned int)	//组合结果

static struct work_struct work;

static void work_handler(struct work_struct * work)
{
	ssleep(1);
	condition = 1;
	wake_up_interruptible(&wq);
	printk("workqueue_handler\n");
}

static irqreturn_t irq_handler(int irq_num, void* dev)
{
	schedule_work(&work);
	printk("irq_handler  irq_num = %d\n",irq_num);
	return IRQ_HANDLED;
}

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

static inline void adc_start(void)
{
	*adccon |= (0x1 << 0);
}

static inline unsigned short adc_read(void)
{
	unsigned short value = *adcdat0 & 0x3ff;
	
	return value;	 
}

//设置输入通道
static inline void set_channel(unsigned char num)
{
	*adccon &= ~(0x7 << 3);
	*adccon |= (num << 3);
}

static int open(struct inode * node, 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)
{
	unsigned short data = 0;
	size_t len_cp = (sizeof(data) < len) ? sizeof(data) : len;
	condition = 0;
	adc_start();
	wait_event_interruptible(wq, condition);
	data = adc_read();
	copy_to_user(buf, &data, len_cp);

	//printk("adc read ...\n");
	return len_cp;
}

//cmd作为特定的命令标识符,用于指示要执行的具体设备特定操作.
//arg作为命令的参数传递给驱动程序
static long ioctl(struct file * file, unsigned int cmd, unsigned long arg)
{
	unsigned char num = 0;
	if(CMD_SET_CHENNEL == cmd)	//传进来的命令是否是设置通道的
	{
		num = arg;				//设置通道的参数,设置通道几
		set_channel(num);
	}
	else
		return -EINVAL;			//返回-1,在应用层打印错误号

	return 0;
}

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

static struct file_operations fops = 
{
	.owner = THIS_MODULE,
	.open = open,
	.read = read,
	.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 = 0;
	ret = misc_register(&misc);
	if ( ret < 0 )
		goto error_misc_register;

	ret = request_irq(pdev->resource[3].start, irq_handler, IRQF_DISABLED, "irq_adc", NULL);
	if ( ret < 0 )
		goto error_request_irq;

	init_waitqueue_head(&wq);
												//计算要映射的物理地址范围的大小(字节数)								
	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_WORK(&work, work_handler);

	printk("adc_driver probe *clkcon = %lx	##############################\n", *clkcon);
	return 0;

error_misc_register:
	misc_deregister(&misc);
	printk("misc_register error...\n");
	return ret;

error_request_irq:
	disable_irq(pdev->resource[3].start);
	free_irq(pdev->resource[3].start, NULL);
	misc_deregister(&misc);
	printk("error_request_irq...\n");
	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, NULL);
	misc_deregister(&misc);
	printk("adc_driver remove	##############################\n");

	return 0;
}

//该结构体用于描述一个平台设备的驱动程序
static struct platform_driver dri = 
{
	.probe = probe,
	.remove = remove,
	.driver = 
	{
		.name = DEV_NAME	//驱动程序的名称
		//当内核发现一个平台设备的名称与一个驱动程序的名称相同时
		//会尝试将它们进行匹配并调用驱动程序的探测函数
	}
};
	//probe 是一个指向驱动程序中探测函数的指针
	//当内核发现一个设备与这个驱动程序匹配时,
	//会调用这个探测函数来初始化设备并建立设备与驱动程序之间的关联。

	//remove 指向驱动程序中的移除函数。当设备从系统中移除时,
	//内核会调用这个函数来执行一些清理操作,释放设备占用的资源。

static int __init adc_driver_init(void)
{
	//向内核注册注册平台设备驱动程序
	int ret = platform_driver_register(&dri);
	if( ret < 0 )
		goto err_platform_driver_register;

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

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

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

module_init(adc_driver_init);
module_exit(adc_driver_exit);
MODULE_LICENSE("GPL");
相关推荐
cominglately2 小时前
centos单机部署seata
linux·运维·centos
魏 无羡3 小时前
linux CentOS系统上卸载docker
linux·kubernetes·centos
CircleMouse3 小时前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
木子Linux3 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算
mit6.8243 小时前
Ubuntu 系统下性能剖析工具: perf
linux·运维·ubuntu
鹏大师运维4 小时前
聊聊开源的虚拟化平台--PVE
linux·开源·虚拟化·虚拟机·pve·存储·nfs
watermelonoops4 小时前
Windows安装Ubuntu,Deepin三系统启动问题(XXX has invalid signature 您需要先加载内核)
linux·运维·ubuntu·deepin
滴水之功4 小时前
VMware OpenWrt怎么桥接模式联网
linux·openwrt
ldinvicible5 小时前
How to run Flutter on an Embedded Device
linux
YRr YRr6 小时前
解决Ubuntu 20.04上编译OpenCV 3.2时遇到的stdlib.h缺失错误
linux·opencv·ubuntu