时间轴

2025-11-15

init


平台总线

Linux 内核中,平台总线(Platform Bus)是一个用于管理和连接”平台设备(platform device)”和”平台驱动(platform driver)”的抽象层。它充当了平台设备(platform device)和平台驱动(platform driver)之间的桥梁,负责将它们进行匹配和绑定。

它主要用于那些 不通过标准总线(如 PCI、USB、I²C、SPI)发现设备 的设备,而是直接集成在 SoC(System on Chip)或者主板上的设备

设备/驱动/平台总线的关系

通过使用平台总线模型,将设备驱动和平台设备进行了分离。这样一来,我们只需编写一份通用的驱动代码即可,然后针对不同的平台设备进行配置,这就大大减少了重复编写代码的工作量,并提高了驱动代码的重用性。

当我们需要将驱动移植到不同的平台时,只需对硬件相关的部分进行适配即可,其他部分可以保持不变。

平台总线下,设备(platform_device)和驱动(platform_driver)是两个独立对象

  • 设备来自 DT/板级文件/平台代码
  • 驱动来自模块加载/编译进内核
  • 驱动卸载不等于设备消失

注册platform设备

platform_device结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// include/linux/platform_device.h
struct platform_device {
const char *name;// 设备的名称,用于唯一标识设备
int id;// 设备的 ID,可以用于区分同一种设备的不同实例
bool id_auto;// 表示设备的 ID 是否自动生成
struct device dev;// 表示平台设备对应的 struct device 结构体,用于设备的基本管理和操作
u64 platform_dma_mask;
struct device_dma_parameters dma_parms;
u32 num_resources;// 设备资源的数量
struct resource *resource;// 指向设备资源的指针

const struct platform_device_id *id_entry;// 指向设备的 ID 表项的指针,用于匹配设备和驱动
/*
* Driver name to force a match. Do not set directly, because core
* frees it. Use driver_set_override() to set or clear it.
*/
const char *driver_override;// 强制设备与指定驱动匹配的驱动名称

/* MFD cell pointer */
struct mfd_cell *mfd_cell;// 指向多功能设备(MFD)单元的指针,用于多功能设备的描述

/* arch specific additions */
struct pdev_archdata archdata;// 用于存储特定于架构的设备数据
};
  • const char *name:设备的名称,用于唯一标识设备。必须提供一个唯一的名称,以便内核能够正确识别和管理该设备。
  • int id:设备的 ID,可以用于区分同一种设备的不同实例。这个参数是可选的,如果不需要使用 ID 进行区分,可以将其设置为-1
  • struct device dev:表示平台设备对应的 struct device 结构体,用于设备的基本管理和操作。必须为该参数提供一个有效的 struct device 对象,该结构体的 release 方法必须要实现,否则在编译的时候会报错。
  • u32 num_resources:设备资源的数量。如果设备具有资源(如内存区域、中断等),则需要提供资源的数量。
  • struct resource *resource:指向设备资源的指针。如果设备具有资源,需要提供一个指向资源数组的指针,会在下个小节对该结构体进行详细的讲解。

resource结构体

1
2
3
4
5
6
7
8
9
10
//include/linux/ioport.h
struct resource {
resource_size_t start;/* 资源的起始地址 */
resource_size_t end;/* 资源的结束地址 */
const char *name;/* 资源的名称 */
unsigned long flags;/* 资源的标志位 */
unsigned long desc;/* 资源的描述信息 */
struct resource *parent, *sibling, *child;/* 指向子资源的指针 */
};

中最重要的是前四个参数,每个参数的具体介绍如下所示:

  • resource_size_t start:资源的起始地址。它表示资源的起始位置或者起始寄存器的地址。
  • resource_size_t end:资源的结束地址。它表示资源的结束位置或者结束寄存器的地址。
  • const char *name:资源的名称。它是一个字符串,用于标识和描述资源。
  • unsigned long flags:资源的标志位。它包含了一些特定的标志,用于表示资源的属性或者特征。例如,可以用标志位来指示资源的可用性、共享性、缓存属性等。flags 参数的具体取值和含义可以根据系统和驱动的需求进行定义和解释,但通常情况下,它用于表示资源的属性、特征或配置选项。下面是一些常见的标志位及其可能的含义
分类 标志位 说明
资源类型相关标志位 IORESOURCE_IO 资源是 I/O 端口资源
IORESOURCE_MEM 资源是内存资源
IORESOURCE_REG 资源是寄存器偏移量
IORESOURCE_IRQ 资源是中断资源
IORESOURCE_DMA 资源是 DMA(直接内存访问)资源
资源属性与特征相关标志位 IORESOURCE_PREFETCH 资源是无副作用的可预取资源
IORESOURCE_READONLY 资源只读
IORESOURCE_CACHEABLE 资源支持缓存
IORESOURCE_RANGELENGTH 资源的范围长度
IORESOURCE_SHADOWABLE 资源可被影子资源替代
IORESOURCE_SIZEALIGN 资源大小字段对齐
IORESOURCE_STARTALIGN 起始地址字段对齐
IORESOURCE_MEM_64 资源是 64 位内存资源
IORESOURCE_WINDOW 资源由桥接器转发
IORESOURCE_MUXED 资源被软件复用
IORESOURCE_SYSRAM 资源是系统 RAM(修饰符)
状态与控制相关标志位 IORESOURCE_EXCLUSIVE 用户空间无法映射此资源
IORESOURCE_DISABLED 资源当前被禁用
IORESOURCE_UNSET 地址尚未分配给资源
IORESOURCE_AUTO 地址由系统自动分配
IORESOURCE_BUSY 驱动程序标记资源为繁忙

platform_device_register()

项目 说明
函数定义 int platform_device_register(struct platform_device *pdev);
头文件 #include <linux/platform_device.h>
参数 pdev 指向 platform_device 结构体的指针,描述要注册的平台设备,包括设备名称、资源、设备 ID 等信息。
功能 将平台设备注册到内核,使其能够参与设备资源分配和驱动匹配。
返回值 成功:返回 0;失败:返回负数错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// include/linux/platform_device.h
#define platform_get_device_id(pdev) ((pdev)->id_entry)

#define dev_is_platform(dev) ((dev)->bus == &platform_bus_type)
#define to_platform_device(x) container_of((x), struct platform_device, dev)

extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);


// drivers/base/platform.c

/**
* platform_device_register - add a platform-level device
* @pdev: platform device we're adding
*/
int platform_device_register(struct platform_device *pdev)
{
device_initialize(&pdev->dev);
setup_pdev_dma_masks(pdev);
return platform_device_add(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_register);
  • device_initialize(&pdev->dev)对 pdev->dev 进行初始化。pdev->dev 是 struct platform_device 结构体中的一个成员,它表示平台设备对应的 struct device 结构体。通过调用device_initialize 函数,对 pdev->dev 进行一些基本的初始化工作,例如设置设备的引用计数、设备的类型等。
  • setup_pdev_dma_masks根据平台设备的架构数据来设置 pdev的架构相关数据。这个函数的具体实现可能与具体的架构相关,它主要用于在不同的架构下对平台设备进行特定的设置。
  • platform_device_add 函数 , 将平台设备 pdev 添加到内核中 。platform_device_add 函数会完成平台设备的添加操作,包括将设备添加到设备层级结构中、添加设备的资源等。它会返回一个 int 类型的结果,表示设备添加的结果。

platform_device_register 函数的主要作用是将 platform_device 结构体描述的平台设备注册到内核中,包括设备的初始化、添加到 platform 总线和设备层级结构、添加设备资源等操作。

通过该函数,平台设备被注册后,就能够参与设备的资源分配和驱动的匹配过程。函数的返回值可以用于判断设备注册是否成功。

platform_device_unregister()

项目 说明
函数定义 void platform_device_unregister(struct platform_device *pdev);
头文件 #include <linux/platform_device.h>
参数 pdev 指向要取消注册的平台设备的 platform_device 结构体指针
功能 取消注册已注册的平台设备,从内核中移除设备,并进行资源清理。
返回值 无返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// drivers/base/platform.c

/**
* platform_device_unregister - unregister a platform-level device
* @pdev: platform device we're unregistering
*
* Unregistration is done in 2 steps. First we release all resources
* and remove it from the subsystem, then we drop reference count by
* calling platform_device_put().
*/
void platform_device_unregister(struct platform_device *pdev)
{
platform_device_del(pdev);
platform_device_put(pdev);
}
EXPORT_SYMBOL_GPL(platform_device_unregister);
  • platform_device_del 函数,用于将设备从 platform 总线的设备列表中移除。它会将设备从设备层级结构中移除,停止设备的资源分配和驱动的匹配。
  • platform_device_put 函数,用于减少对设备的引用计数。这个函数会检查设备的引用计数,如果引用计数减为零,则会释放设备结构体和相关资源。通过减少引用计数,可以确保设备在不再被使用时能够被释放。

platform_device_unregister 函数的作用是取消注册已经注册的平台设备,从内核中移除设备 。它先调用 platform_device_del 函 数 将设备从设层级结构中移除 , 然后调用platform_device_put 函数减少设备的引用计数,确保设备在不再被使用时能够被释放。

示例

platform_device的release回调函数必须实现,否则可能编译不过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>

#define MEM_START_ADDR 0xFDD60000
#define MEM_END_ADDR 0xFDD60004
#define IRQ_NUMBER 101

static struct resource my_resources[] = {
{
.start = MEM_START_ADDR, // 内存资源起始地址
.end = MEM_END_ADDR, // 内存资源结束地址
.flags = IORESOURCE_MEM, // 标记为内存资源
},
{
.start = IRQ_NUMBER, // 中断资源号
.end = IRQ_NUMBER, // 中断资源号
.flags = IORESOURCE_IRQ, // 标记为中断资源
},
};

static void my_platform_device_release(struct device *dev)
{
// 释放资源的回调函数
}

static struct platform_device my_platform_device = {
.name = "my_platform_device", // 设备名称
.id = -1, // 设备ID
.num_resources = ARRAY_SIZE(my_resources), // 资源数量
.resource = my_resources, // 资源数组
.dev.release = my_platform_device_release, // 释放资源的回调函数
};

static int __init my_platform_device_init(void)
{
int ret;

ret = platform_device_register(&my_platform_device); // 注册平台设备
if (ret) {
printk(KERN_ERR "Failed to register platform device\n");
return ret;
}

printk(KERN_INFO "Platform device registered\n");
return 0;
}

static void __exit my_platform_device_exit(void)
{
platform_device_unregister(&my_platform_device); // 注销平台设备
printk(KERN_INFO "Platform device unregistered\n");
}

module_init(my_platform_device_init);
module_exit(my_platform_device_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");

加载后在/sys/bus/platform/devices 目录下,可以看到我们创建的 my_platform_device 设备

注册platform驱动

platform_driver结构体

1
2
3
4
5
6
7
8
9
10
11
// include/linux/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:平台设备的移除函数指针。当平台设备从系统中移除时,该函数将被调用以执行清理和释放资源的操作。
  • shutdown:平台设备的关闭函数指针。当系统关闭时,该函数将被调用以执行与平台设备相关的关闭操作。
  • suspend:平台设备的挂起函数指针。当系统进入挂起状态时,该函数将被调用以执行与平台设备相关的挂起操作。
  • resume:平台设备的恢复函数指针。当系统从挂起状态恢复时,该函数将被调用以执行与平台设备相关的恢复操作。
  • driver:包含了与设备驱动程序相关的通用数据,它是 struct device_driver 类型的实例。其中包括驱动程序的名称、总线类型、模块拥有者、属性组数组指针等信息,该结构体的 name参数需要与platform_device.name 参数相同才能匹配成功,从而进入 probe 函数
  • id_table:指向 struct platform_device_id 结构体数组的指针,用于匹配平台设备和驱动程序之间的关联关系。通过该关联关系,可以确定哪个平台设备与该驱动程序匹配,和.driver.name起到相同的作用,但是优先级高于.driver.name
  • prevent_deferred_probe:一个布尔值,用于确定是否阻止延迟探测。如果设置为 true,则延迟探测将被禁用。

device_driver()结构体

struct device_driver设备模型(Device Model)层) 的抽象,它描述的是 驱动程序本身的身份和行为,例如:

  • 驱动名称、所属总线、模块信息
  • 设备匹配表(设备树、ACPI)
  • 驱动操作回调:probe / remove / suspend / resume
  • sysfs 属性和电源管理

核心思想:它关注的是驱动和设备之间的匹配与生命周期管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// include/linux/device/driver.h
struct device_driver {
const char *name;// 驱动的名字,用于匹配设备。
struct bus_type *bus;// 驱动所属的总线类型,例如 PCI、USB、I2C 等。

struct module *owner;// 指向驱动所属模块 (struct module),用于模块引用计数。
const char *mod_name; /* used for built-in modules */ //模块名字,如果是内置驱动而非模块的话,会用这个名字。

bool suppress_bind_attrs; /* disables bind/unbind via sysfs */ //如果为 true,就禁用了通过 sysfs 手动绑定/解绑设备。
enum probe_type probe_type;// 驱动探测方式,决定驱动如何被自动绑定到设备(例如正常探测或延迟探测)。

const struct of_device_id *of_match_table;// 用于 设备树匹配(Device Tree)
const struct acpi_device_id *acpi_match_table;// 用于 ACPI 设备匹配。

int (*probe) (struct device *dev);// 核心函数,当驱动匹配到设备时调用,初始化设备。
void (*sync_state)(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);// 用于电源管理,恢复。
// 用于 暴露驱动和设备的 sysfs 属性,可以通过 /sys/bus/... 或 /sys/class/... 查看和操作。
const struct attribute_group **groups;
const struct attribute_group **dev_groups;

const struct dev_pm_ops *pm;// 指向电源管理操作的结构体,包含 suspend/resume、runtime PM 等回调函数。
void (*coredump) (struct device *dev);// 当设备出现严重错误时,可以触发驱动的 coredump 回调,用于调试。

struct driver_private *p;// 内核内部使用,存放与驱动相关的私有数据,例如绑定的设备列表。
};

platform_driver_register()

项目 说明
函数定义 int platform_driver_register(struct platform_driver *driver);
头文件 #include <linux/platform_device.h>
参数 driver 指向 platform_driver 结构体的指针,描述要注册的平台驱动程序,包括属性和回调函数。
功能 将平台驱动程序注册到内核,使内核能够与特定平台设备匹配并调用相应回调函数。
返回值 成功:返回 0;
失败:返回负数错误码
1
2
3
4
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
extern int __platform_driver_register(struct platform_driver *,
struct module *);

这个宏用于简化平台驱动程序的注册过程。它将实际的注册函数 __platform_driver_register 与当前模块(驱动程序)关联起来。宏的参数 drv 是一个指向 struct platform_driver 结构体的指针,描述了要注册的平台驱动程序的属性和回调函数。THIS_MODULE 是一个宏,用于获取当前模块的指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// drivers/base/platform.c
/**
* __platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
* @owner: owning module/driver
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;// 将平台驱动程序的所有权设置为当前模块
drv->driver.bus = &platform_bus_type;// 将平台驱动程序的总线类型设置为平台总线
drv->driver.probe = platform_drv_probe;// 设置平台驱动程序的探测函数
drv->driver.remove = platform_drv_remove;// 设置平台驱动程序的移除函数
drv->driver.shutdown = platform_drv_shutdown;// 设置平台驱动程序的关机函数

return driver_register(&drv->driver);// 将平台驱动程序注册到内核
}
EXPORT_SYMBOL_GPL(__platform_driver_register);

通过这些操作,__platform_driver_register 函数将平台驱动程序与内核关联起来,并确保内核能够正确识别和调用驱动程序的各种回调函数,以实现与平台设备的交互和管理。函数的返回值表示注册过程的执行状态,以便在需要时进行错误处理。

platform_driver_unregister()

项目 说明
函数定义 int platform_driver_register(struct platform_driver *driver);
头文件 #include <linux/platform_device.h>
参数 driver 指向 platform_driver 结构体的指针,描述要注册的平台驱动程序,包括属性和回调函数。
功能 将平台驱动程序注册到内核,使内核能够与特定平台设备匹配并调用相应回调函数。
返回值 成功:返回 0;失败:返回负数错误码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// include/linux/platform_device.h
extern void platform_driver_unregister(struct platform_driver *);

// drivers/base/platform.c

/**
* platform_driver_unregister - unregister a driver for platform-level devices
* @drv: platform driver structure
*/
void platform_driver_unregister(struct platform_driver *drv)
{
driver_unregister(&drv->driver);
}
EXPORT_SYMBOL_GPL(platform_driver_unregister);

// drivers/base/driver.c

/**
* driver_unregister - remove driver from system.
* @drv: driver.
*
* Again, we pass off most of the work to the bus-level call.
*/
void driver_unregister(struct device_driver *drv)
{
// 检查传入的设备驱动程序指针和 p 成员是否有效
if (!drv || !drv->p) {
WARN(1, "Unexpected driver unregister!\n");
return;
}
driver_remove_groups(drv, drv->groups);// 移除与设备驱动程序关联的属性组
bus_remove_driver(drv);// 从总线中移除设备驱动程序
}
EXPORT_SYMBOL_GPL(driver_unregister);
  • bus_remove_driver 函数,用于从总线中移除设备驱动程序。该函数会执行以下操作:
    • 从总线驱动程序列表中移除指定的设备驱动程序。
    • 调用与设备驱动程序关联的 remove 回调函数(如果有定义)。
    • 释放设备驱动程序所占用的资源和内存。
    • 最终销毁设备驱动程序的数据结构。

通过调用 driver_unregister 函数,可以正确地注销设备驱动程序,并在注销过程中进行必要的清理工作。这样可以避免资源泄漏和其他问题。在调用该函数后,应避免继续使用已注销的设备驱动程序指针,因为该驱动程序已不再存在于内核中

platform_get_resource()

在驱动程序中获取平台设备的资源信息,并根据这些信息进行后续的操作和配置

项目 说明
函数定义 struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int num);
头文件 #include <linux/platform_device.h>
参数 pdev 指向要获取资源的平台设备(platform_device)结构体指针
参数 type 资源类型,如: IORESOURCE_MEM:内存资源, IORESOURCE_IO:I/O 资源, IORESOURCE_IRQ:中断资源
参数 num 资源索引号,用于选择设备中同类型的第 num 个资源
功能 从平台设备的资源数组中获取指定类型和索引的资源信息
返回值 成功:返回指向 struct resource 的指针;失败或资源不存在:返回 NULL

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>

static int my_platform_driver_probe(struct platform_device *pdev)
{
struct resource *res_mem, *res_irq;

// 方法1:直接访问 platform_device 结构体的资源数组
if (pdev->num_resources >= 2) {
struct resource *res_mem = &pdev->resource[0];
struct resource *res_irq = &pdev->resource[1];

// 使用获取到的硬件资源进行处理
printk("Method 1: Memory Resource: start = 0x%llx, end = 0x%llx\n",
res_mem->start, res_mem->end);
printk("Method 1: IRQ Resource: number = %lld\n", res_irq->start);
}

// 方法2:使用 platform_get_resource() 获取硬件资源
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res_mem) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENODEV;
}

res_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
if (!res_irq) {
dev_err(&pdev->dev, "Failed to get IRQ resource\n");
return -ENODEV;
}

// 使用获取到的硬件资源进行处理
printk("Method 2: Memory Resource: start = 0x%llx, end = 0x%llx\n",
res_mem->start, res_mem->end);
printk("Method 2: IRQ Resource: number = %lld\n", res_irq->start);

return 0;
}

static int my_platform_driver_remove(struct platform_device *pdev)
{
// 设备移除操作
return 0;
}

static struct platform_driver my_platform_driver = {
.driver = {
.name = "my_platform_device", // 与 platform_device.c 中的设备名称匹配
.owner = THIS_MODULE,
},
.probe = my_platform_driver_probe,
.remove = my_platform_driver_remove,
};

static int __init my_platform_driver_init(void)
{
int ret;

ret = platform_driver_register(&my_platform_driver); // 注册平台驱动
if (ret) {
printk("Failed to register platform driver\n");
return ret;
}

printk("Platform driver registered\n");
return 0;
}

static void __exit my_platform_driver_exit(void)
{
platform_driver_unregister(&my_platform_driver); // 注销平台驱动
printk("Platform driver unregistered\n");
}

module_init(my_platform_driver_init);
module_exit(my_platform_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");

LED灯平台总线示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>

struct device_test{
dev_t dev_num; //设备号
int major ; //主设备号
int minor ; //次设备号
struct cdev cdev_test; // cdev
struct class *class; //类
struct device *device; //设备
char kbuf[32];
unsigned int *vir_gpio_dr;
};

struct device_test dev1;


/*打开设备函数*/
static int cdev_test_open(struct inode *inode, struct file *file)
{
file->private_data=&dev1;//设置私有数据
printk("This is cdev_test_open\r\n");

return 0;
}

/*向设备写入数据函数*/
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
struct device_test *test_dev=(struct device_test *)file->private_data;

if (copy_from_user(test_dev->kbuf, buf, size) != 0) // copy_from_user:用户空间向内核空间传数据
{
printk("copy_from_user error\r\n");
return -1;
}
if(test_dev->kbuf[0]==1){ //如果应用层传入的数据是1,则打开灯
*(test_dev->vir_gpio_dr) = 0x8000c040; //设置数据寄存器的地址
printk("test_dev->kbuf [0] is %d\n",test_dev->kbuf[0]); //打印传入的数据
}
else if(test_dev->kbuf[0]==0) //如果应用层传入的数据是0,则关闭灯
{
*(test_dev->vir_gpio_dr) = 0x80004040; //设置数据寄存器的地址
printk("test_dev->kbuf [0] is %d\n",test_dev->kbuf[0]); //打印传入的数据
}
return 0;
}

/**从设备读取数据*/
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{

struct device_test *test_dev=(struct device_test *)file->private_data;

if (copy_to_user(buf, test_dev->kbuf, strlen( test_dev->kbuf)) != 0) // copy_to_user:内核空间向用户空间传数据
{
printk("copy_to_user error\r\n");
return -1;
}

printk("This is cdev_test_read\r\n");
return 0;
}

static int cdev_test_release(struct inode *inode, struct file *file)
{
printk("This is cdev_test_release\r\n");
return 0;
}

/*设备操作函数*/
struct file_operations cdev_test_fops = {
.owner = THIS_MODULE, //将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块
.open = cdev_test_open,
.read = cdev_test_read,
.write = cdev_test_write,
.release = cdev_test_release,
};

static int my_platform_driver_probe(struct platform_device *pdev)
{
struct resource *res_mem;
int ret;
res_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res_mem) {
dev_err(&pdev->dev, "Failed to get memory resource\n");
return -ENODEV;
}

/*注册字符设备驱动*/
/*1 创建设备号*/
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_name"); //动态分配设备号
if (ret < 0)
{
goto err_chrdev;
}
printk("alloc_chrdev_region is ok\n");

dev1.major = MAJOR(dev1.dev_num); //获取主设备号
dev1.minor = MINOR(dev1.dev_num); //获取次设备号

printk("major is %d \r\n", dev1.major); //打印主设备号
printk("minor is %d \r\n", dev1.minor); //打印次设备号
/*2 初始化cdev*/
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_test, &cdev_test_fops);

/*3 添加一个cdev,完成字符设备注册到内核*/
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if(ret<0)
{
goto err_chr_add;
}
/*4 创建类*/
dev1. class = class_create(THIS_MODULE, "test");
if(IS_ERR(dev1.class))
{
ret=PTR_ERR(dev1.class);
goto err_class_create;
}
/*5 创建设备*/
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
if(IS_ERR(dev1.device))
{
ret=PTR_ERR(dev1.device);
goto err_device_create;
}
dev1.vir_gpio_dr=ioremap(res_mem->start,4); //将物理地址转化为虚拟地址
if(IS_ERR(dev1.vir_gpio_dr))
{
ret=PTR_ERR(dev1.vir_gpio_dr); //PTR_ERR()来返回错误代码
goto err_ioremap;
}


return 0;

err_ioremap:
iounmap(dev1.vir_gpio_dr);

err_device_create:
class_destroy(dev1.class); //删除类

err_class_create:
cdev_del(&dev1.cdev_test); //删除cdev

err_chr_add:
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号

err_chrdev:
return ret;
}

static int my_platform_driver_remove(struct platform_device *pdev)
{
// 设备移除操作
return 0;
}

static struct platform_driver my_platform_driver = {
.driver = {
.name = "my_platform_device", // 与 platform_device.c 中的设备名称匹配
.owner = THIS_MODULE,
},
.probe = my_platform_driver_probe,
.remove = my_platform_driver_remove,
};

static int __init my_platform_driver_init(void)
{
int ret;

ret = platform_driver_register(&my_platform_driver); // 注册平台驱动
if (ret) {
printk("Failed to register platform driver\n");
return ret;
}

printk("Platform driver registered\n");
return 0;
}

static void __exit my_platform_driver_exit(void)
{
/*注销字符设备*/
unregister_chrdev_region(dev1.dev_num, 1); //注销设备号
cdev_del(&dev1.cdev_test); //删除cdev
device_destroy(dev1.class, dev1.dev_num); //删除设备
class_destroy(dev1.class); //删除类
platform_driver_unregister(&my_platform_driver); // 注销平台驱动
printk("Platform driver unregistered\n");
}

module_init(my_platform_driver_init);
module_exit(my_platform_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("topeet");

.remove 是设备被移除时调用的,而不是驱动卸载时调用的。.remove 在“设备消失”时执行,不代表“驱动要卸载,所以注销字符设备逻辑必须放在 module_exit()。