linux系列目录: linux基础篇(一)——GCC和Makefile编译过程 linux基础篇(二)——静态和动态链接 ARM裸机篇(一)——i.MX6ULL介绍 ARM裸机篇(二)——i.MX6ULL启动过程 ARM裸机篇(三)——i.MX6ULL第一个裸机程序 ARM裸机篇(四)——重定位和地址无关码 ARM裸机篇(五)——异常和中断 linux系统移植篇(一)—— linux系统组成 linux系统移植篇(二)—— Uboot使用介绍 linux系统移植篇(三)—— Linux 内核使用介绍 linux系统移植篇(四)—— 根文件系统使用介绍 linux驱动开发篇(一)—— Linux 内核模块介绍 linux驱动开发篇(二)—— 字符设备驱动框架 linux驱动开发篇(三)—— 总线设备驱动模型 linux驱动开发篇(四)—— platform平台设备驱动 linux驱动开发篇(五)—— linux驱动面向对象的编程思想 linux驱动开发篇(六)—— 设备树的引入
一、平台设备驱动
在设备驱动模型中,引入总线的概念可以对驱动代码和设备信息进行分离。但是驱动中总线的概念是软件层面的一种抽象,与我们 SOC 中物理总线的概念并不严格相等:
- 物理总线:芯片与各个功能外设之间传送信息的公共通信干线,其中又包括数据总线、地址总线和控制总线,以此来传输各种通信时序。
- 驱动总线:负责管理设备和驱动。制定设备和驱动的匹配规则,一旦总线上注册了新的设备或者是新的驱动,总线将尝试为它们进行配对。
一般对于 I2C、 SPI、 USB 这些常见类型的物理总线来说, Linux 内核会自动创建与之相应的驱动总线,因此 I2C 设备、 SPI 设备、 USB 设备自然是注册挂载在相应的总线上。但是,实际项目开发中还有很多结构简单的设备,对它们进行控制并不需要特殊的时序。它们也就没有相应的物理总线,比如 led、 rtc 时钟、蜂鸣器、按键等等, Linux 内核将不会为它们创建相应的驱动总线。为了使这部分设备的驱动开发也能够遵循设备驱动模型, Linux 内核引入了一种虚拟的总线——平台总线(platform bus)。
平台设备驱动的核心依然是 Linux 设备驱动模型,平台设备使用 platform_device 结构体来进行表示,其继承了设备驱动模型中的device 结构体。而平台驱动使用 platform_driver 结构体来进行表示,其则是继承了设备驱动模型中的 device_driver结构体。
1、平台总线
内核中使用 bus_type 来抽象描述系统中的总线,平台总线结构体原型如下所示: platform_bus_type 结 构 体 (内 核 源码/driver/base/platform.c)
struct bus_type platform_bus_type = {
.name = "platform",
.dev_groups = platform_dev_groups,
.match = platform_match,
.uevent = platform_uevent,
.dma_configure = platform_dma_configure,
.pm = &platform_dev_pm_ops,
};
EXPORT_SYMBOL_GPL(platform_bus_type);
内核用 platform_bus_type 来描述平台总线,该总线在 linux 内核启动的时候自动进行注册。
这里重点是 platform 总线的 match 函数指针,该函数指针指向的函数将负责实现平台总线和平台设备的匹配过程。对于每个驱动总线,它都必须实例化该函数指针。 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);
if (pdev->driver_override)
return !strcmp(pdev->driver_override, drv->name);
if (of_driver_match_device(dev, drv))
return 1;
if (acpi_driver_match_device(dev, drv))
return 1;
if (pdrv->id_table)
return platform_match_id(pdrv->id_table, pdev) != NULL;
return (strcmp(pdev->name, drv->name) == 0);
}
platform 总线提供了四种匹配方式,并且这四种方式存在着优先级:设备树机 制 >ACPI 匹配模式 >id_table 方式 > 字符串比较。
2、平台设备
内核使用 platform_device 结构体来描述平台设备,platform_device 结 构 体 (内 核 源码/include/linux/platform_device.h)结构体原型如下:
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u32 num_resources;
struct resource *resource;
const struct platform_device_id *id_entry;
char *driver_override;
struct mfd_cell *mfd_cell;
struct pdev_archdata archdata;
};
- name: 设备名称,总线进行匹配时,会比较设备和驱动的名称是否一致;
- id: 指定设备的编号, Linux 支持同名的设备,而同名设备之间则是通过该编号进行区分;
- dev: Linux 设备模型中的 device 结构体, linux 内核大量使用了面向对象思想, platform_device通过继承该结构体可复用它的相关代码,方便内核管理平台设备;
- num_resources: 记录资源的个数,当结构体成员 resource 存放的是数组时,需要记录 resource数组的个数,内核提供了宏定义 ARRAY_SIZE 用于计算数组的个数;
- resource: 平台设备提供给驱动的资源,如 irq, dma,内存等等。该结构体会在接下来的内容进行讲解;
- id_entry: 平台总线提供的另一种匹配方式,原理依然是通过比较字符串,这部分内容会在平台总线小节中讲,这里的 id_entry 用于保存匹配的结果;
平台设备的工作是为驱动程序提供设备信息, 设备信息包括硬件信息和软件信息两部分。
- 硬件信息:驱动程序需要使用到什么寄存器,占用哪些中断号、内存资源、 IO 口等等
- 软件信息:以太网卡设备中的 MAC 地址、 I2C 设备中的设备地址、 SPI 设备的片选信号线等等。
对于硬件信息,使用结构体 struct resource 来保存设备所提供的资源,比如设备使用的中断编号,寄存器物理地址等,结构体原型如下:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
};
- name: 指定资源的名字,可以设置为 NULL;
- start、 end: 指定资源的起始地址以及结束地址
- flags: 用于指定该资源的类型,在 Linux 中,资源包括 I/O、 Memory、 Register、 IRQ、 DMA、Bus 等多种类型,最常见的有以下几种:
资源宏定义 | 描述 |
---|
IORESOURCE_IO | 用于 IO 地址空间,对应于 IO 端口映射方式 | IORESOURCE_MEM | 用于外设的可直接寻址的地址空间 | IORESOURCE_IRQ | 用于指定该设备使用某个中断 | IORESOURCE_DMA | 用于指定使用的 DMA 通道 |
注册/注销平台设备API原型:
int platform_device_register(struct platform_device *pdev);
void platform_device_unregister(struct platform_device *pdev);
3、平台驱动
内核中使用 platform_driver 结构体来描述平台驱动,platform_driver 结 构 体 (内 核 源 码/include/platform_device.h)结构体原型如下所示:
struct platform_driver {
int (*probe)(struct platform_device *);
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;
};
- probe: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当总线为设备和驱动匹配上之后,会回调执行该函数。我们一般通过该函数,对设备进行一系列的初始化。
- remove: 函数指针,驱动开发人员需要在驱动程序中初始化该函数指针,当我们移除某个平台设备时,会回调执行该函数指针,该函数实现的操作,通常是 probe 函数实现操作的逆过程。
- driver: Linux 设备模型中用于抽象驱动的 device_driver 结构体, platform_driver 继承该结构体,也就获取了设备模型驱动对象的特性;
- id_table: 表示该驱动能够兼容的设备类型。
platform_device_id 结构体原型如下所示:
struct platform_device_id {
char name[PLATFORM_NAME_SIZE];
kernel_ulong_t driver_data;
};
在 platform_device_id 这个结构体中,有两个成员,第一个是数组用于指定驱动的名称,总线进行匹配时,会依据该结构体的 name 成员与 platform_device 中的变量 name 进行比较匹配,另一个成员变量 driver_data,则是用于来保存设备的配置。
注册/注销平台驱动API
int platform_driver_register(struct platform_driver *drv);
void platform_driver_unregister(struct platform_driver *drv);
4、平台驱动获取设备信息
在学习平台设备的时候,我们知道平台设备使用结构体 resource 来抽象表示硬件信息,而软件信息则可以利用设备结构体 device 中的成员 platform_data 来保存。先看一下如何获取平台设备中结构体 resource 提供的资源。 platform_get_resource() 函数通常会在驱动的 probe 函数中执行,用于获取平台设备提供的资源结构体,最终会返回一个 struct resource 类型的指针,该函数原型如下:
struct resource *platform_get_resource(struct platform_device *dev,unsigned int type, unsigned int num);
- dev: 指定要获取哪个平台设备的资源;
- type: 指定获取资源的类型,如 IORESOURCE_MEM、 IORESOURCE_IO 等;
- num: 指定要获取的资源编号。每个设备所需要资源的个数是不一定的,为此内核对这些资源进行了编号,对于不同的资源,编号之间是相互独立的。
对于存放在 device 结构体中成员 platform_data 的软件信息,我们可以使用 dev_get_platdata 函数来获取,函数原型如下所示:
static inline void *dev_get_platdata(const struct device *dev)
{
return dev->platform_data;
}
总结一下平台驱动需要实现 probe 函数,当平台总线成功匹配驱动和设备时,则会调用驱动的 probe 函数,在该函数中使用上述的函数接口来获取资源,以初始化设备,最后 填充结构体 platform_driver,调用 platform_driver_register 进行注册。
二、平台设备驱动实验
把平台设备驱动应用到 LED 字符设备驱动的代码中,实现硬件与软件代码相分离。
1、编程思路
- 编写第一个内核模块 led_pdev.c
- 在内核模块中定义一个平台设备,并填充 RGB 灯相关设备信息
- 在该模块入口函数,注册/挂载这个平台设备
- 编写第二个内核模块 led_pdrv.c
- 在内核模块中定义一个平台驱动,在 probe 函数中完成字符设备驱动的创建
- 在该模块入口函数,注册/挂载这个平台驱动
在平台设备总线上,注册/挂载平台设备和平台驱动时,会自动进行配对。配对成功后,回调执行平台驱动的 probe 函数,从而完成字符设备驱动的创建。
2、定义平台设备
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#define CCM_CCGR1 0x20C406C
#define IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04 0x20E006C
#define IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04 0x20E02F8
#define GPIO1_GDIR 0x0209C004
#define GPIO1_DR 0x0209C000
#define CCM_CCGR3 0x020C4074
#define GPIO4_GDIR 0x020A8004
#define GPIO4_DR 0x020A8000
#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020 0x020E01E0
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020 0x020E046C
#define IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019 0x020E01DC
#define IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019 0x020E0468
static struct resource rled_resource[] = {
[0] = DEFINE_RES_MEM(GPIO1_DR, 4),
[1] = DEFINE_RES_MEM(GPIO1_GDIR, 4),
[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO04, 4),
[3] = DEFINE_RES_MEM(CCM_CCGR1, 4),
[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO04, 4),
};
static struct resource gled_resource[] = {
[0] = DEFINE_RES_MEM(GPIO4_DR, 4),
[1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO020, 4),
[3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO020, 4),
};
static struct resource bled_resource[] = {
[0] = DEFINE_RES_MEM(GPIO4_DR, 4),
[1] = DEFINE_RES_MEM(GPIO4_GDIR, 4),
[2] = DEFINE_RES_MEM(IOMUXC_SW_MUX_CTL_PAD_GPIO4_IO019, 4),
[3] = DEFINE_RES_MEM(CCM_CCGR3, 4),
[4] = DEFINE_RES_MEM(IOMUXC_SW_PAD_CTL_PAD_GPIO4_IO019, 4),
};
static void led_release(struct device *dev)
{
}
unsigned int rled_hwinfo[2] = { 4, 26 };
unsigned int gled_hwinfo[2] = { 20, 12 };
unsigned int bled_hwinfo[2] = { 19, 12 };
static struct platform_device rled_pdev = {
.name = "led_pdev",
.id = 0,
.num_resources = ARRAY_SIZE(rled_resource),
.resource = rled_resource,
.dev = {
.release = led_release,
.platform_data = rled_hwinfo,
},
};
static struct platform_device gled_pdev = {
.name = "led_pdev",
.id = 1,
.num_resources = ARRAY_SIZE(gled_resource),
.resource = gled_resource,
.dev = {
.release = led_release,
.platform_data = gled_hwinfo,
},
};
static struct platform_device bled_pdev = {
.name = "led_pdev",
.id = 2,
.num_resources = ARRAY_SIZE(bled_resource),
.resource = bled_resource,
.dev = {
.release = led_release,
.platform_data = bled_hwinfo,
},
};
static __init int led_pdev_init(void)
{
printk("pdev init\n");
platform_device_register(&rled_pdev);
platform_device_register(&gled_pdev);
platform_device_register(&bled_pdev);
return 0;
}
module_init(led_pdev_init);
static __exit void led_pdev_exit(void)
{
printk("pdev exit\n");
platform_device_unregister(&rled_pdev);
platform_device_unregister(&gled_pdev);
platform_device_unregister(&bled_pdev);
}
module_exit(led_pdev_exit);
MODULE_AUTHOR("embedfire");
MODULE_LICENSE("GPL");
3、定义平台驱动
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#define DEV_MAJOR 243
#define DEV_NAME "led"
static struct class *my_led_class;
struct led_data {
unsigned int led_pin;
unsigned int clk_regshift;
unsigned int __iomem *va_dr;
unsigned int __iomem *va_gdir;
unsigned int __iomem *va_iomuxc_mux;
unsigned int __iomem *va_ccm_ccgrx;
unsigned int __iomem *va_iomux_pad;
struct cdev led_cdev;
};
static int led_cdev_open(struct inode *inode, struct file *filp)
{
printk("%s\n", __func__);
struct led_data *cur_led = container_of(inode->i_cdev, struct led_data, led_cdev);
unsigned int val = 0;
val = readl(cur_led->va_ccm_ccgrx);
val &= ~(3 << cur_led->clk_regshift);
val |= (3 << cur_led->clk_regshift);
writel(val, cur_led->va_ccm_ccgrx);
writel(5, cur_led->va_iomuxc_mux);
writel(0x1F838, cur_led->va_iomux_pad);
val = readl(cur_led->va_gdir);
val &= ~(1 << cur_led->led_pin);
val |= (1 << cur_led->led_pin);
writel(val, cur_led->va_gdir);
val = readl(cur_led->va_dr);
val |= (0x01 << cur_led->led_pin);
writel(val, cur_led->va_dr);
filp->private_data = cur_led;
return 0;
}
static int led_cdev_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_cdev_write(struct file *filp, const char __user * buf,
size_t count, loff_t * ppos)
{
unsigned long val = 0;
unsigned long ret = 0;
int tmp = count;
struct led_data *cur_led = (struct led_data *)filp->private_data;
kstrtoul_from_user(buf, tmp, 10, &ret);
val = readl(cur_led->va_dr);
if (ret == 0)
val &= ~(0x01 << cur_led->led_pin);
else
val |= (0x01 << cur_led->led_pin);
writel(val, cur_led->va_dr);
*ppos += tmp;
return tmp;
}
static struct file_operations led_cdev_fops = {
.open = led_cdev_open,
.release = led_cdev_release,
.write = led_cdev_write,
};
static int led_pdrv_probe(struct platform_device *pdev)
{
struct led_data *cur_led;
unsigned int *led_hwinfo;
struct resource *mem_dr;
struct resource *mem_gdir;
struct resource *mem_iomuxc_mux;
struct resource *mem_ccm_ccgrx;
struct resource *mem_iomux_pad;
dev_t cur_dev;
int ret = 0;
printk("led platform driver probe\n");
cur_led = devm_kzalloc(&pdev->dev, sizeof(struct led_data), GFP_KERNEL);
if(!cur_led)
return -ENOMEM;
led_hwinfo = devm_kzalloc(&pdev->dev, sizeof(unsigned int)*2, GFP_KERNEL);
if(!led_hwinfo)
return -ENOMEM;
led_hwinfo = dev_get_platdata(&pdev->dev);
cur_led->led_pin = led_hwinfo[0];
cur_led->clk_regshift = led_hwinfo[1];
mem_dr = platform_get_resource(pdev, IORESOURCE_MEM, 0);
mem_gdir = platform_get_resource(pdev, IORESOURCE_MEM, 1);
mem_iomuxc_mux = platform_get_resource(pdev, IORESOURCE_MEM, 2);
mem_ccm_ccgrx = platform_get_resource(pdev, IORESOURCE_MEM, 3);
mem_iomux_pad = platform_get_resource(pdev, IORESOURCE_MEM, 4);
cur_led->va_dr =
devm_ioremap(&pdev->dev, mem_dr->start, resource_size(mem_dr));
cur_led->va_gdir =
devm_ioremap(&pdev->dev, mem_gdir->start, resource_size(mem_gdir));
cur_led->va_iomuxc_mux =
devm_ioremap(&pdev->dev, mem_iomuxc_mux->start,
resource_size(mem_iomuxc_mux));
cur_led->va_ccm_ccgrx =
devm_ioremap(&pdev->dev, mem_ccm_ccgrx->start,
resource_size(mem_ccm_ccgrx));
cur_led->va_iomux_pad =
devm_ioremap(&pdev->dev, mem_iomux_pad->start,
resource_size(mem_iomux_pad));
cur_dev = MKDEV(DEV_MAJOR, pdev->id);
register_chrdev_region(cur_dev, 1, "led_cdev");
cdev_init(&cur_led->led_cdev, &led_cdev_fops);
ret = cdev_add(&cur_led->led_cdev, cur_dev, 1);
if(ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
device_create(my_led_class, NULL, cur_dev, NULL, DEV_NAME "%d", pdev->id);
platform_set_drvdata(pdev, cur_led);
return 0;
add_err:
unregister_chrdev_region(cur_dev, 1);
return ret;
}
static int led_pdrv_remove(struct platform_device *pdev)
{
dev_t cur_dev;
struct led_data *cur_data = platform_get_drvdata(pdev);
printk("led platform driver remove\n");
cur_dev = MKDEV(DEV_MAJOR, pdev->id);
cdev_del(&cur_data->led_cdev);
device_destroy(my_led_class, cur_dev);
unregister_chrdev_region(cur_dev, 1);
return 0;
}
static struct platform_device_id led_pdev_ids[] = {
{.name = "led_pdev"},
{}
};
MODULE_DEVICE_TABLE(platform, led_pdev_ids);
static struct platform_driver led_pdrv = {
.probe = led_pdrv_probe,
.remove = led_pdrv_remove,
.driver.name = "led_pdev",
.id_table = led_pdev_ids,
};
static __init int led_pdrv_init(void)
{
printk("led platform driver init\n");
my_led_class = class_create(THIS_MODULE, "my_leds");
platform_driver_register(&led_pdrv);
return 0;
}
module_init(led_pdrv_init);
static __exit void led_pdrv_exit(void)
{
printk("led platform driver exit\n");
platform_driver_unregister(&led_pdrv);
class_destroy(my_led_class);
}
module_exit(led_pdrv_exit);
MODULE_AUTHOR("Embedfire");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("the example for platform driver");
4、编译
makefile文件
KERNEL_DIR=../ebf_linux_kernel
ARCH=arm
CROSS_COMPILE=arm-linux-gnueabihf-
export ARCH CROSS_COMPILE
obj-m := led_pdev.o led_pdrv.o
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
modules clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
编译成功后,实验目录下会生成两个名为“led_pdev.ko”、” led_pdrv.ko”的驱动模块文件。
|