Linux —— 驱动——platform平台总线
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、 全局变量定义
adccon
、adcdat0
、clkcon
:这些是指向硬件寄存器的指针,用于控制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_register
和platform_driver_unregister
用于注册和注销平台设备驱动。8. 模块初始化和退出
使用
module_init
和module_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_ADCstatic 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");
主要组成部分
- 宏定义:
DEV_NAME
:设备的名称,这里是"adc"。ADCCON
,ADCDAT0
,CLKCON
:ADC控制器、ADC数据寄存器、时钟控制寄存器的内存映射IO地址。IRQ_NUM
:ADC中断的编号,这里假设有一个IRQ_ADC
宏定义在<mach/irqs.h>
中。- 资源定义(
res
数组):
- 定义了ADC控制器、ADC数据寄存器、时钟控制寄存器和ADC中断的资源。每个资源都有一个起始地址(
.start
)和结束地址(.end
),名称(.name
),以及类型(.flags
)。对于内存映射IO,使用IORESOURCE_IO
;对于中断,使用IORESOURCE_IRQ
。- 平台设备结构体(
dev
):
- 包含了设备的名称(
"adc"
)、ID(-1
表示此设备类型只有一个实例)、释放函数(release
,这里为空实现)、资源数量(通过sizeof(res) / sizeof(res[0])
计算)和资源列表(res
)。- 模块初始化/退出函数:
adc_device_init
:使用platform_device_register
函数注册平台设备。如果注册失败,则通过goto
语句跳转到错误处理代码,并尝试注销设备。adc_device_exit
:使用platform_device_unregister
函数注销平台设备。- 模块许可:
- 通过
MODULE_LICENSE("GPL")
声明该模块使用GPL(GNU通用公共许可证)发布。注意事项
- 这段代码假设了
IRQ_ADC
和相关的内存映射地址在<mach/irqs.h>
或其他包含文件中已经定义。这通常取决于具体的硬件平台和内核配置。- 释放函数
release
在这里是空的,但在实际应用中,如果设备分配了任何资源(如内存、中断号等),则应该在这里进行清理。- 该模块本身不直接处理ADC数据的读取或中断服务例程的编写,这些功能通常需要由与这个设备关联的设备驱动程序来实现。
- 在实际部署中,还需要考虑内核配置(如CONFIG_PLATFORM_DRIVER等)和可能的依赖关系(如特定的硬件抽象层或板级支持包)。