Linux驱动之平台总线

article/2025/7/28 2:51:08

Linux驱动之平台总线

参考视频地址

【北京迅为】嵌入式学习之Linux驱动(第六期_平台总线_全新升级)_基于RK3568_哔哩哔哩_bilibili

平台总线介绍

一、什么是平台总线模型?

​ 平台总线模型也叫platform总线模型。平台总线是Linux系统虚拟出来的总线。

二、平台总线模型的使用

​ 平台总线模型将一个驱动分成了两个部分,分别是device.c和driver.c,device.c用来描述硬件,driver.c用来控制硬件。

三、平台总线模型是如何工作的

​ 平台总线通过字符串比较,将 name 相同的 device.c 和 driver.c 匹配到一起来控制硬件。

在这里插入图片描述

​ 平台总线原则:先分离,后搭档

四、平台总线模型的优点

  1. 减少编写重复代码,提高效率。
  2. 提高代码的利用率。

注册platform设备

platform_device

struct platform_device用于描述平台设备(如SoC上的外设)的硬件资源,包括:

  • 寄存器地址(通过struct resource
  • 中断号
  • 其他设备特定资源

这个结构体定义在include/linux/platform_device.h文档中,结构体如下所示:

struct platform_device {const char *name;int id;                   struct device dev;        // 内嵌的通用设备结构体u32 num_resources;        // 资源数量struct resource *resource; // 资源数组指针// 其他可选成员(不同内核版本可能略有差异)const struct platform_device_id *id_entry;bool id_auto;             // 用于自动生成设备IDchar *driver_override;struct mfd_cell *mfd_cell; // 用于MFD(多功能设备)子系统struct pdev_archdata archdata; // 架构特定数据
};

关键成员说明

  • name: 设备名称,需与驱动中的platform_driver.name匹配。
  • id: 设备实例编号(如同一设备有多个实例)。
  • num_resourcesresource: 描述硬件资源(如内存、中断),通过struct resource数组定义。
  • dev: 继承自通用设备模型,包含设备树、电源管理等核心信息。

resource

struct resource用于描述硬件资源(如内存区域、中断号等)的地址范围和属性,是平台设备和驱动之间传递资源信息的基础结构。头文件定义在include/linux/ioport.h, 结构体如下所示:

// 定义路径:include/linux/ioport.h
struct resource {resource_size_t start;  // 资源起始地址(物理地址或中断号)resource_size_t end;    // 资源结束地址const char *name;       // 资源名称(可选,用于调试)unsigned long flags;    // 资源类型标志(如IORESOURCE_MEM、IORESOURCE_IRQ)unsigned long desc;     // 资源描述符(特定用途,如DMA通道、总线号等)struct resource *parent, *sibling, *child; // 资源树管理指针(用于层次化资源分配)
};

关键成员说明

  • startend
    表示资源的地址范围。例如:
    • 内存资源:start=0xFE000000, end=0xFE000FFF(表示0xFE000000~0xFE000FFF的内存区域)。
    • 中断资源:start=42, end=42(表示中断号为42)。
  • flags
    标志位,用于标识资源类型,常见值包括:
    • IORESOURCE_TYPE_BITS: 源类型位域掩码
    • IORESOURCE_MEM:内存资源
    • IORESOURCE_IRQ:中断资源
    • IORESOURCE_IO:I/O端口资源
    • IORESOURCE_REG: 寄存器偏移(特定场景使用)
    • IORESOURCE_DMA:DMA通道资源
    • IORESOURCE_BUS:总线资源(如PCI总线号)
    • IORESOURCE_PREFETCH:可预取内存(如PCI设备)
    • IORESOURCE_MEM_64:64位内存地址
    • IORESOURCE_WINDOW:桥接器的地址窗口
    • IORESOURCE_DISABLED:资源已被禁用
    • IORESOURCE_UNSET:资源未初始化
    • IORESOURCE_AUTO:自动分配资源
  • desc
    附加描述符,具体含义依赖于资源类型。例如:
    • 对于中断资源,可能表示中断触发方式(高电平、边沿等)。
    • 对于DMA资源,可能表示DMA通道号。
  • 资源树指针(parent/sibling/child
    用于构建资源管理树,确保资源分配不冲突(如内存区域或中断号的嵌套分配)。

struct resource举个例子:

static struct resource mem_res[] = {[0] = {.start = 0xFE000000,  //寄存器起始地址.end = 0xFE000FFF,    //寄存器终止地址.flags = IORESOURCE_MEM,.name = "device_memory",},[1] = {.start = 13,.end = 13,.flags = IORESOURCE_MEM,.name = "device1_memory",},
};

platform 设备加载和卸载

platform_device_register
int platform_device_register(struct platform_device *pdev)

功能:加载platform设备。

示例代码

#include <linux/platform_device.h>
#include <linux/module.h>// 1. 定义资源(内存和中断)
static struct resource my_device_resources[] = {[0] = {.start = 0xFE000000,   // 寄存器起始地址.end = 0xFE000FFF,     // 寄存器结束地址.flags = IORESOURCE_MEM, // 内存资源},[1] = {.start = 42,           // 中断号.end = 42,.flags = IORESOURCE_IRQ, // 中断资源},
};// 2. 定义platform_device结构体
static struct platform_device my_device = {.name = "my_platform_device", // 设备名称(需与驱动匹配).id = -1,                     // 设备实例ID(-1表示单实例).num_resources = ARRAY_SIZE(my_device_resources),.resource = my_device_resources,
};// 3. 模块初始化函数(加载设备)
static int __init my_device_init(void) {int ret;ret = platform_device_register(&my_device);if (ret) {pr_err("Failed to register platform device\n");return ret;}pr_info("Platform device registered\n");return 0;
}module_init(my_device_init);

platform_device_unregister
void platform_device_unregister(struct platform_device *pdev)

功能:卸载platform设备。

示例代码

// 4. 模块退出函数(卸载设备)
static void __exit my_device_exit(void) {platform_device_unregister(&my_device);pr_info("Platform device unregistered\n");
}module_exit(my_device_exit);// 5. 模块元信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Platform Device Example");

测试实验

​ 我么定义一个资源名为my_resources的 结构体数组中,具体内容如下:

​ 内存资源:

​ 起始地址:MEM_START_ADDR(0xFEC30004)

​ 结束地址:MEM_END_ADDR(0xFEC30008)

​ 标记:IORESOURCE_MEM

​ 中断资源:

​ 中断资源号:IRQ_NUMBER(112)

​ 标记:IORESOURCE_IRQ

​ 代码路径:/home/topeet/Linux/my-test/36_platform_device/platform_dev.c,代码如下所示:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>#define MEM_START_ADDR 0xFEC3004
#define MEM_END_ADDR   0xFEC3008
#define IRQ_NUMBER  112static struct resource my_resource[] = {[0] = {.start = MEM_START_ADDR,.end = MEM_END_ADDR,.flags = IORESOURCE_MEM,},[1] = {.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,.num_resources = ARRAY_SIZE(my_resource),.resource = my_resource,.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 resistered.\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("YAN");
MODULE_VERSION("v1.0");

实验现象:

//加载驱动
root@topeet:/run# insmod platform_dev.ko

​ 加载完驱动后,查看路径/sys/bus/platform/devices/,是否有存在my_platform_device,如下图所示:

在这里插入图片描述


注册platform驱动

platform_driver

platform_driver用于实现平台设备的软件驱动逻辑,其核心功能包括:

  • 设备探测与初始化:通过probe函数初始化硬件。
  • 资源释放:通过remove函数卸载驱动时释放资源。
  • 电源管理:通过suspend/resume函数实现设备的低功耗管理。
  • 设备与驱动匹配:通过id_table或设备树(Device Tree)的compatible字段匹配设备。
// 定义路径: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;      // 设备ID匹配表(用于非设备树匹配)bool prevent_deferred_probe;                    // 是否禁止延迟探测(内核高级特性)
};

关键成员

  • probe
    驱动与设备匹配成功后自动调用,负责初始化硬件、申请资源(如内存映射、中断注册)。
  • remove
    驱动卸载时调用,释放资源(如释放内存、注销中断)。
  • driver
    通用驱动结构体,必须包含.name字段(与platform_device.name匹配),或通过.of_match_table匹配设备树节点。
  • id_table
    设备ID匹配表,用于非设备树场景下的设备驱动匹配,示例如下:
static const struct platform_device_id my_driver_ids[] = {{ "my_platform_device", 0 }, // 匹配platform_device.name{ /* Sentinel */ }
};

注册于卸载函数

int platform_driver_register(struct platform_driver *drv):注册驱动。void platform_driver_unregister(struct platform_driver *drv):注销驱动。

测试实验

​ 代码路径/home/topeet/Linux/my-test/37_platform_driver/platform_drv.c,代码如下所示:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>int my_platform_probe(struct platform_device *pdev)
{printk("my_platform_driver: Probing platform device.\n");return 0;
}int my_platform_remove(struct platform_device *pdev)
{printk("my_platform_driver: Removing platform device.\n");return 0;
}static struct platform_driver my_platform_driver = {.probe = my_platform_probe,.remove = my_platform_remove,.driver = {.name = "my_platform_device",.owner = THIS_MODULE,},};static int __init my_platform_driver_init(void)
{int ret;ret = platform_driver_register(&my_platform_driver);if( ret ){printk(KERN_ERR "Failed to register platform driver.\n");return ret;}printk(KERN_INFO "my_platform_driver: Platform driver initialized.\n");return 0;
}static void __exit my_platform_driver_exit(void)
{platform_driver_unregister(&my_platform_driver);printk(KERN_INFO "my_platform_driver: Platform driver exited.\n");
}module_init(my_platform_driver_init);
module_exit(my_platform_driver_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("YAN");
MODULE_VERSION("v1.0");

实验现象

// 加载device驱动
insmod platform_dev.ko// 加载platform_drv.ko
insmod platform_drv.ko//输出probe 函数的printk
[ 2930.477208] Platform device resistered.
[ 2957.043437] my_platform_driver: Probing platform device.
[ 2957.044529] my_platform_driver: Platform driver initialized.//查看/sys下路径是否有对应生成my_platform_device文件
root@topeet:/run# ls /sys/bus/platform/drivers/my_platform_device/
bind  module  my_platform_device  uevent  unbind

probe函数

​ 驱动是要控制硬件的。但是平台总线模型对硬件的描述是在设备(device)中的。所以在驱动(driver)中,我们需要得到设备(device)中的硬件资源。当设备(device)和驱动(driver)匹配成功以后,会执行驱动(driver)中的probe函数,所以我们要在probe函数中拿到设备(device)中的硬件资源。所以,本节课的重点是要如何拿到,怎么拿到这些硬件资源呢

获取设备资源接口API

platform_get_resource()

功能:获取特定类型的硬件资源(如内存、I/O、中断等)。
原型

struct resource *platform_get_resource(struct platform_device *pdev, unsigned int type, unsigned int index
);

参数

  • pdev:关联的platform_device指针。
  • type:资源类型(如IORESOURCE_MEMIORESOURCE_IRQ)。
  • index:资源索引(从0开始,按类型分组)。

返回值

  • 成功:返回指向struct resource的指针。
  • 失败:返回NULL(资源不存在或索引越界)。

调用示例:

// 获取第0个内存资源
struct resource *mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!mem_res) {dev_err(&pdev->dev, "Memory resource not found\n");return -ENODEV;
}
platform_get_resource_byname()

功能:通过资源名称获取特定资源(需在resource中定义name字段)。
原型

struct resource *platform_get_resource_byname(struct platform_device *pdev, unsigned int type, const char *name
);

参数

  • name:资源名称(需与resource.name匹配)。

调用示例

// 定义资源时指定名称
static struct resource my_resources[] = {[0] = {.start = 0xFE000000,.end = 0xFE000FFF,.flags = IORESOURCE_MEM,.name = "device_memory",},
};// 通过名称获取资源
struct resource *mem_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "device_memory");
platform_get_irq()

功能:获取设备的中断号(IRQ)。
原型

int platform_get_irq(struct platform_device *pdev, unsigned int index
);

参数

  • index:中断资源的索引(从0开始)。

返回值

  • 成功:返回中断号(正数)。
  • 失败:返回负数错误码(如-ENXIO表示中断未找到)。

示例

int irq_num = platform_get_irq(pdev, 0);
if (irq_num < 0) {dev_err(&pdev->dev, "Failed to get IRQ: %d\n", irq_num);return irq_num;
}
devm_ioremap_resource()

功能:将物理内存资源映射到内核虚拟地址空间,并自动管理资源释放(无需手动iounmap)。
原型

void __iomem *devm_ioremap_resource(struct device *dev, const struct resource *res
);

参数

  • dev:设备指针(通常为&pdev->dev)。
  • res:已通过platform_get_resource获取的资源指针。

返回值

  • 成功:返回映射后的虚拟地址指针。
  • 失败:返回ERR_PTR()错误码(如-ENOMEM)。

示例

void __iomem *reg_base = devm_ioremap_resource(&pdev->dev, mem_res);
if (IS_ERR(reg_base)) {return PTR_ERR(reg_base);
}
devm_request_irq()

功能:注册中断处理函数,并自动释放中断(防止资源泄漏)。
原型

int devm_request_irq(struct device *dev,unsigned int irq,irq_handler_t handler,unsigned long flags,const char *name,void *dev_id
);

参数

  • irq:中断号(通过platform_get_irq获取)。
  • handler:中断处理函数。
  • flags:中断标志(如IRQF_TRIGGER_RISING)。
  • name:中断名称(显示在/proc/interrupts)。
  • dev_id:传递给中断处理函数的私有数据。

示例

ret = devm_request_irq(&pdev->dev, irq_num, my_irq_handler,IRQF_TRIGGER_RISING, "my_device", NULL);
if (ret) {dev_err(&pdev->dev, "Failed to request IRQ\n");return ret;
}
devm_platform_ioremap_resource()

功能:直接通过索引映射内存资源,简化设备树资源获取流程。
原型

void __iomem *devm_platform_ioremap_resource(struct platform_device *pdev,unsigned int index
);

示例

void __iomem *reg_base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(reg_base)) {return PTR_ERR(reg_base);
}

测试实验

​ 代码路径:/home/topeet/Linux/my-test/38_platform_probe/platform_drv.c, 这里主要是probe()函数的实现,获取device resource中的内存资源和中断资源,代码如下所示:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/mod_devicetable.h>int my_platform_probe(struct platform_device *pdev)
{struct resource *res_mem, *res_irq;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 Reosurce: number = %lld\n", res_irq->start);return 0;
}int my_platform_remove(struct platform_device *pdev)
{printk("my_platform_driver: Removing platform device.\n");return 0;
}const struct platform_device_id mydriver_id_table = {.name = "my_platform_device",
};static struct platform_driver my_platform_driver = {.probe = my_platform_probe,.remove = my_platform_remove,.driver = {.name = "my_platform_device",.owner = THIS_MODULE,},.id_table = &mydriver_id_table,};static int __init my_platform_driver_init(void)
{int ret;ret = platform_driver_register(&my_platform_driver);if( ret ){printk(KERN_ERR "Failed to register platform driver.\n");return ret;}printk(KERN_INFO "my_platform_driver: Platform driver initialized.\n");return 0;
}static void __exit my_platform_driver_exit(void)
{platform_driver_unregister(&my_platform_driver);printk(KERN_INFO "my_platform_driver: Platform driver exited.\n");
}module_init(my_platform_driver_init);
module_exit(my_platform_driver_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("YAN");
MODULE_VERSION("v1.0");

实验现象

// 加载platform_dev.ko 和 加载platform_drv.ko
insmod platform_dev.ko
insmod platform_drv.ko// 加载驱动完成后,我们能看到probe() 函数里面加的printk(),信息被打印
[14427.361364] Method 2: Memory Resource: start = 0xfec3004, end = 0xfec3008
[14427.361415] Method 2: IRQ Reosurce: number = 112
[14427.361565] my_platform_driver: Platform driver initialized.

LED驱动改为platform

​ 代码路径:/home/topeet/Linux/my-test/39_platform_led, 设备驱动代码路径:device/platform_dev.c, 代码如下所示:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>#define DR_MEM_START_ADDR 0xFEC30004
#define DR_MEM_END_ADDR   0xFEC30008
#define DIR_MEM_START_ADDR 0xFEC3000C
#define DIR_MEM_END_ADDR   0xFEC30010
#define IRQ_NUMBER  112static struct resource my_resource[] = {[0] = {.start = DR_MEM_START_ADDR,  // 配置GPIO2C4 数据寄存器地址。.end = DR_MEM_END_ADDR,.flags = IORESOURCE_MEM,},[1] = {.start = DIR_MEM_START_ADDR,  // 配置GPIO2C4 方向寄存器地址。.end = DIR_MEM_END_ADDR,.flags = IORESOURCE_MEM,},[2] = {.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,.num_resources = ARRAY_SIZE(my_resource),.resource = my_resource,.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 resistered.\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("YAN");
MODULE_VERSION("v1.0");

​ 接着是驱动程序路径:driver/platform_drv.c, 代码如下所示:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/ioport.h>
#include <linux/mod_devicetable.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/errno.h>
#include <linux/io.h>struct device_test
{dev_t dev_num;int major;int minor;struct cdev myCdev;struct class *myClass;struct device *myDevice;char kbuf[32];unsigned int *vir_gpio_dr;unsigned int *vir_gpio_dir;
};struct device_test test;static int myCdev_open(struct inode *inode, struct file *file)
{printk("This is myCdev_open.\n");file->private_data = &test;return 0;
}static ssize_t myCdev_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{struct device_test *test = (struct device_test *)file->private_data;if( copy_to_user(buf, test->kbuf, strlen(test->kbuf)) != 0 ){printk("copy_to_user error.\n");return -1;}printk("This is myCdev_read.\n");return 0;
}static ssize_t myCdev_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{struct device_test *test = (struct device_test *)file->private_data;if( copy_from_user(test->kbuf, buf, size ) != 0 ){printk("copy_from_user error.\n");return -1;}// 如果应用层传入的数据是1,则设置GPIO为高电平if( test->kbuf[0] == 1 ){*(test->vir_gpio_dr) |= 0x00100010;}// 如果应用层传入的数据是0,则设置GPIO为低电平else if( test->kbuf[0] == 0 ){// *(test->vir_gpio_dr) |= 0x00100000;*(test->vir_gpio_dr) = (*(test->vir_gpio_dr) | (1 << 20)) & ~(1 << 4);}return 0;
}static int myCdev_release(struct inode *inode, struct file *file)
{return 0;
}struct file_operations myCdev_fops = {.owner = THIS_MODULE,.open = myCdev_open,.read = myCdev_read,.write = myCdev_write,.release = myCdev_release,
};int my_platform_probe(struct platform_device *pdev)
{int ret;struct resource *res1_mem, *res2_mem,*res_irq;res1_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);if( !res1_mem ){dev_err(&pdev->dev, "Failed to get memory resource 1.\n");return -ENODEV;}res2_mem = platform_get_resource(pdev, IORESOURCE_MEM, 1);if( !res2_mem ){dev_err(&pdev->dev, "Failed to get memory resource 2.\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_1: start = 0x%llx, end = 0x%llx\n", res1_mem->start, res1_mem->end);printk("Method 2: Memory Resource_2: start = 0x%llx, end = 0x%llx\n", res2_mem->start, res2_mem->end);printk("Method 2: IRQ Reosurce: number = %lld\n", res_irq->start);ret = alloc_chrdev_region(&test.dev_num, 0, 1, "alloc_name");if( ret < 0 )goto err_chrdev;test.major = MAJOR(test.dev_num);test.minor = MINOR(test.dev_num);test.myCdev.owner = THIS_MODULE;cdev_init(&test.myCdev, &myCdev_fops);ret = cdev_add(&test.myCdev, test.dev_num, 1);if( ret < 0 )goto err_cdev_add;test.myClass = class_create(THIS_MODULE, "test");if( IS_ERR(test.myClass) ){ret = PTR_ERR(test.myClass);goto err_class_create;}test.myDevice = device_create(test.myClass, NULL, test.dev_num, NULL, "test");if( IS_ERR(test.myDevice) ){ret = PTR_ERR(test.myDevice);goto err_device_create;}//将物理地址转化为虚拟地址test.vir_gpio_dr = ioremap(res1_mem->start, 4);if( IS_ERR(test.vir_gpio_dr) ){ret = PTR_ERR(test.vir_gpio_dr);goto err_gpio_ioremap_dr;}test.vir_gpio_dir = ioremap(res2_mem->start, 4);if( IS_ERR(test.vir_gpio_dir) ){ret = PTR_ERR(test.vir_gpio_dir);goto err_gpio_ioremap_dir;}*(test.vir_gpio_dir) |= 0x00100030;return 0;
err_gpio_ioremap_dir:iounmap(test.vir_gpio_dr);
err_gpio_ioremap_dr:device_destroy(test.myClass, test.dev_num);
err_device_create:class_destroy(test.myClass);
err_class_create:cdev_del(&test.myCdev);
err_cdev_add:unregister_chrdev_region(test.dev_num, 1);
err_chrdev:return 0;
}int my_platform_remove(struct platform_device *pdev)
{printk("my_platform_driver: Removing platform device.\n");return 0;
}const struct platform_device_id mydriver_id_table = {.name = "my_platform_device",
};static struct platform_driver my_platform_driver = {.probe = my_platform_probe,.remove = my_platform_remove,.driver = {.name = "my_platform_device",.owner = THIS_MODULE,},.id_table = &mydriver_id_table,};static int __init my_platform_driver_init(void)
{int ret;ret = platform_driver_register(&my_platform_driver);if( ret ){printk(KERN_ERR "Failed to register platform driver.\n");return ret;}printk(KERN_INFO "my_platform_driver: Platform driver initialized.\n");return 0;
}static void __exit my_platform_driver_exit(void)
{cdev_del(&test.myCdev);unregister_chrdev_region(test.dev_num, 1);device_destroy(test.myClass, test.dev_num);class_destroy(test.myClass);iounmap(test.vir_gpio_dr);iounmap(test.vir_gpio_dir);platform_driver_unregister(&my_platform_driver);printk(KERN_INFO "my_platform_driver: Platform driver exited.\n");
}module_init(my_platform_driver_init);
module_exit(my_platform_driver_exit);MODULE_LICENSE("GPL");
MODULE_AUTHOR("YAN");
MODULE_VERSION("v1.0");

​ APP代码还是沿用《字符设备基础》里面,章节“点亮一个LED灯”的代码。

实验现象

​ 按《字符设备基础》文档里面连接方式,连接上LED灯,加载platform_dev.ko,加载platform_drv.ko,并运行app应用,正常要能看到LED灯闪烁。


总结

在这里插入图片描述


http://www.hkcw.cn/article/rAbnIvXkij.shtml

相关文章

《Python语言程序设计》2018 第4章第9题3重量和价钱的对比,利用第7章的概念来解答你

利用类来解答这个问题。 pack1, price1 50, 24.59 pack2, price2 25, 11.99class result:def __init__(self,pack,price):self.pack packself.price pricedef set_pack(self):return self.packdef set_price(self):return self.pricedef get_result(self):return self.pric…

Parametric Retrieval Augmented Generation

Parametric Retrieval Augmented Generation 3. Methodology 3.1 Problem Formulation and Overview 文中原始符号数学表示额外解释LLM L L L大模型的简化表示LLM parameters θ \theta θ大模型的参数表示user query q q q用户的输入external corpus K K K K { d 1 , d 2 ,…

8088 单板机 汇编 NMI 中断程序示例 (脱离 DOS 环境)

; ; nmi_demo.asm - 8088 单板机 NMI 中断演示程序 ; 脱离 DOS 环境&#xff0c;直接运行在裸机上 ; ; 硬件配置假设: ; - 8088 CPU 4.77MHz ; - 8259 PIC (可编程中断控制器) ; - 8255 PPI (可编程外设接口) 连接 LED ; - 7 段数码管显示 ; - NMI 按钮连接到 NMI 引脚PORT_P…

HRMS数据模型:解密组织的数字基因库与智能管理引擎

摘要 随着数字化转型浪潮席卷全球&#xff0c;人力资源管理系统&#xff08;HRMS&#xff09;正升级为企业的“数字基因库”&#xff0c;承载着组织、岗位与人员的动态管理使命。本文基于创新的“三维动态耦合模型”&#xff0c;系统剖析HRMS数据模型设计理念、关键组件与业务…

Nature|张泽民团队提出CM模型新视角 | 单细胞如何走向系统生态?|细胞模块概念新研究范式

端午节儿童节&#xff0c;假期到了&#xff0c;不知道大家有没有安排出游&#xff0c;去哪里放松、吃到什么特别的美食&#xff1f;长大之后&#xff0c;儿童节就悄悄地从我们的生活中“毕业”了&#xff0c;忙碌中偶尔的童心和好奇&#xff0c;还是要有的。 今天分享最近学习…

题单:最大公约数(辗转相除法)

题目描述 所谓 “最大公约数&#xff08;GCD&#xff09;” &#xff0c;是指所有公约数中最大的那个&#xff0c;例如 12 和 1818 的公约数有 1,2,3,6 &#xff0c;所以 12 和 18 的最大公约数为 6 。 辗转相除法&#xff0c;又名欧几里德算法&#xff08;Euclidean Algorit…

75岁薛家燕谈小17岁男友 甜蜜恋情持续12年

近日,75岁的资深艺人薛家燕在接受采访时谈及与小17岁男友Andy的恋情,难掩甜蜜。她表示男友多年来对她一直很好。两人通过朋友介绍相识,最初通过电子邮件交流。薛家燕曾因年龄差距犹豫退缩,担心对方只是一时喜欢,甚至一度提出分手。但Andy以真诚打动了她,两人相恋12年,尽…

女子骑电动三轮闯卡强行上高速 收费站工作人员尽力拦截未果

5月31日,有网友在抖音平台发布了一段行车记录仪视频。视频显示,在陕西省渭南市华州收费站,一名女子骑着电动三轮车强行闯卡驶上了高速公路。根据视频内容,当天下午6点45分左右,这名女子骑着电动三轮车紧跟在一辆汽车后面。收费站工作人员发现后,试图让她退回去,但她并未…

新规施行拒绝刷脸有依据了 保护个人信息安全

近年来,刷脸技术在识别个人信息方面的应用日益广泛,从小区门禁、酒店登记到交通出行和金融支付,在经济社会的各个领域几乎都能见到“刷脸”技术的应用。然而,这种便捷性背后也隐藏着不容忽视的风险。为规范人脸识别技术的使用并保护个人信息安全,国家互联网信息办公室和公…

河南水库水位下降现千佛石窟 佛像多有残损引发关注

近日,河南省鹤壁市淇县夺丰水库水位下降后,一处千佛石窟显露出来,引发网友关注。该石窟洞壁布满佛像,但不少佛头和手臂几乎都消失不见。淇县县委宣传部一负责人表示,这处石窟当地人一直都知道,民间称其为天竺石窟或千佛洞。该负责人曾在十多年前两次进入石窟参观。夺丰水…

郑钦文回应晋级:再打两盘都没问题 体力充沛信心足

郑钦文鏖战3盘,耗时2小时47分钟,以2-1击败萨姆索诺娃,晋级法网女单8强,刷新了个人在法网的最佳战绩。赛后接受采访时,她表示自己还有很多能量,甚至开玩笑说如果女子比赛有五盘制,她再打两盘也没问题。这场比赛非常激烈,对手发挥出色,给郑钦文施加了很大压力,她在底线…

当地称瘦弱骆驼主人此前已养死一只 另一只现状堪忧

近日,多名网友在社交平台上发帖称,在福建省福州市平潭县流水镇路边发现一只疑似被遗弃的骆驼。这只骆驼体型瘦弱,趴伏在地上,毛发稀疏,周围没有任何遮蔽,引起了广泛关注。6月1日,一名当地居民表示,5月9日她曾路过该路段,看到有两只骆驼趴在路边;今天早上再去查看时,…

现场球迷合唱《日不落》送给郑钦文 法网晋级喜迎海鲜大餐

5月31日,中国选手郑钦文在2025年法国网球公开赛32强战中表现出色,以6-3、6-4直落两盘击败18岁的加拿大资格赛黑马姆博科,挺进16强,追平个人纪录。比赛结束后,她在巴黎街头漫步并享受了一顿海鲜大餐。首盘比赛中,郑钦文发球状态极佳,一发得分率达78%,轰出3记ACE球,并通…

12.springCloud AlibabaSentinel实现熔断与限流

目录 一、Sentinel简介 1.官网 2.Sentinel 是什么 3.Sentinel 的历史 4.Sentinel 基本概念 资源 规则 5.Sentinel 功能和设计理念 (1).流量控制 什么是流量控制 流量控制设计理念 (2).断降级 什么是熔断降级 熔断降级设计理念 (3).系统自适应保护 6.主要工作机制…

【GPT入门】第40课 vllm与ollama特性对比,与模型部署

【GPT入门】第40课 vllm与ollama特性对比&#xff0c;与模型部署 1.两种部署1.1 vllm与ollama特性对比2. vllm部署2.1 服务器准备2.1 下载模型2.2 提供模型服务 1.两种部署 1.1 vllm与ollama特性对比 2. vllm部署 2.1 服务器准备 在autodl 等大模型服务器提供商&#xff0c;…

PTA-根据已有类Worker,使用LinkedList编写一个WorkerList类,实现计算所有工人总工资的功能。

目录 1.问题描述 2.函数接口定义&#xff1a; 3.裁判测试程序样例&#xff1a; 4.输入和输出样例 输入样例&#xff1a; 输出样例&#xff1a; 5.实现代码 1.问题描述 Main类&#xff1a;在main方法中&#xff0c;调用constructWorkerList方法构建一个Worker对象链表…

Maven概述,搭建,使用

一.Maven概述 Maven是Apache软件基金会的一个开源项目,是一个有优秀的项目构建(创建)工具,它用来帮助开发者管理项目中的jar,以及jar之间的依赖关系,完成项目的编译,测试,打包和发布等工作. 我在当前学习阶段遇到过的jar文件: MySQL官方提供的JDBC驱动文件,通常命名为mysql-…

基于Canvas实现抽奖转盘

本案例基于画布组件、显式动画&#xff0c;实现的一个自定义抽奖圆形转盘。包含如下功能&#xff1a; 通过画布组件Canvas&#xff0c;画出抽奖圆形转盘。通过显式动画启动抽奖功能。通过自定义弹窗弹出抽中的奖品。 一、案例效果截图 案例运行效果如图11-28所示。 图11-27 …

【 SpringCloud | 微服务 网关 】

单体架构时我们只需要完成一次用户登录、身份校验&#xff0c;就可以在所有业务中获取到用户信息。而微服务拆分后&#xff0c;每个微服务都独立部署&#xff0c;这就存在一些问题&#xff1a; 每个微服务都需要编写登录校验、用户信息获取的功能吗&#xff1f; 当微服务之间调…

Vue2之3v-model在组件中的应用以及sync修饰符

文章目录 v-model的原理带value属性组件间传值的普通写法解析演示 根据v-model的原理 简写带有value属性组件之间的传值解析演示 使用sync修饰符&#xff0c;自定义属性名进行组件传值解析案例 v-model的原理 v-model 原理 原理&#xff1a;v-model本质上是一个语法糖。例如应…