当前位置: 首页 > news >正文

imx6ULL从应用程序到驱动程序

文章目录

  • 应用程序
  • 驱动程序
    • 注册主函数
    • 注册platfrom_device
    • 注册platfrom_drive(platfrom_device 和 platfrom_drive没有注册先后之分,谁先注册都可以)
    • platfrom_device 和 platfrom_drive 匹配成功

应用程序

应用程序比较简单,就是通过open来打开一个字符设备,再通过write来改变led状态

int main(int argc, char** argv)
{int fd;char status = 0;if (argc != 3){printf("Usage: %s <dev> <on|off>\n", argv[0]);printf("  eg: %s /dev/myled on\n", argv[0]);printf("  eg: %s /dev/myled off\n", argv[0]);return -1;}//openfd = open(argv[1], O_RDWR);if (fd == -1){printf("can not open %s\n", argv[1]);printf("errno=%d\n",perror);return -1;}// write//strcmp()用于比较两个字符串是否相同,一样则返回0if (strcmp(argv[2], "on") == 0){status = 1;write(fd, &status, 1);}else{status = 0;write(fd, &status, 1);}close(fd);return 0;}

驱动程序

注册主函数

当用户程序调用 open(“/dev/mydevice”, O_RDWR) 会经过这几个步骤

  1. 触发系统调用:应用程序通过 glibc 库调用 open(),进入内核态。
  2. VFS 处理请求:内核的虚拟文件系统(VFS)解析路径 /dev/mydevice,发现这是一个设备文件。
  3. 查找设备驱动:VFS 根据设备文件的主设备号(Major Number)找到对应的驱动程序。
  4. 调用驱动函数:VFS 从驱动注册的 file_operations 结构体中获取 .open 函数指针, 执行驱动实现的 mydevice_open() 函数。

所以为了让VFS能够从驱动注册的 file_operations 结构体中获取 .open 函数指针
要先

static ssize_t led_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos)
{struct inode *inode = file_inode(filp);//得到节int minor = iminor(inode);//从节点里取出次设备号char status;//在驱动程序里定义,属于内核空间int ret;//这里加个ret是因为copy_from_user函数有返回值//如果不使用返回值会有警告ret = copy_from_user(&status, buf, 1);//拷贝到val,buf:用户空间,拷贝一个字节p_ledopr -> ctrl(minor,status);//根据次设备号控制ledreturn 1;
}static int led_open(struct inode *inode, struct file *filp)
{int minor = iminor(inode);//得到次设备号printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);p_ledopr -> init(minor);//根据次设备号初始化ledreturn 0;}static struct file_operations led_fop = {.owner	 = THIS_MODULE,.open 	 = led_open,.write	 = led_write,};

系统怎么知道在哪里找这个file_operations,所以要通过register_chrdev把这个注册到内核

/*入口函数*/
//动态加载:模块可随时通过 insmod 或 modprobe 加载到运行中的内核。
static int __init led_init(void)
{int err;printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);//调用 register_chrdev 向内核注册设备,获取主设备号并绑定 file_operations。/*仅调用 register_chrdev:设备号已注册,但 /dev 下没有设备节点,应用程序无法通过 open("/dev/...") 访问设备。所以还要调用 device_create,自动生成设备节点(如 /dev/my_device0)*/major = register_chrdev(0,"jollyled",&led_fop);//内核设备号注册与操作函数绑定的入口/*内核同时提供了class_create()函数,可以用它来创建一个类,这个类存放于sys下面,创建了这个类,再调用device_create()函数来在/dev目录下创建相应的设备节点。*///class_create :/sys/class/<class_name>//device_create:/sys/class/<class_name>/<device>led_class = class_create(THIS_MODULE,"jollyled_class");err = PTR_ERR(led_class);if (IS_ERR(led_class)) {printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);unregister_chrdev(major, "jollyled");return -1;}return 0;
}

有入口函数就有出口函数

/*出口函数*/
//模块可通过 rmmod 卸载,释放资源。
static void __exit led_exit(void)
{printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);class_destroy(led_class);unregister_chrdev(major, "jollyled");//第二个参数代表设备名,但在函数的实现源码中没有用到,没有什么意义。}

出入口函数必要性:

  • 确保内核资源管理的正确性和安全性。
  • 支持模块的动态加载与卸载,提升系统灵活性。
  • 避免资源泄漏,保障内核稳定性。

注册platfrom_device

platfrom_device用于设备硬件的描述

static struct resource resources[] = {{		//引脚第几组第几个.start = GROUP_PIN(3,1),//中断类型.flags = IORESOURCE_IRQ,//设备名.name = "Jolly_led_pin",},{.start = GROUP_PIN(5,8),.flags = IORESOURCE_IRQ,.name = "Jolly_led_pin",},};static struct platform_device board_A_led_dev = {.name = "Jolly_led",//用于和platfrom_drive匹配的是这个名字.num_resources = ARRAY_SIZE(resources),.resource = resources,.dev = {.release = led_dev_release,},
};

注册进内核

//用于向内核注册一个平台设备(struct platform_device),描述设备的硬件资源(如内存地址、中断号、设备名称等)。
static int __init led_dev_init(void)
{int err;err = platform_device_register(&board_A_led_dev);   return 0;
}static void __exit led_dev_exit(void)
{platform_device_unregister(&board_A_led_dev);
}

注册platfrom_drive(platfrom_device 和 platfrom_drive没有注册先后之分,谁先注册都可以)

//函数原型const struct of_device_id *of_match_device()
static const struct of_device_id Zane_leds[] = {//of_device_id 用于device和driver的match{ .compatible = "Jolly,leddrv" },//前面的Jolly是厂商名,这个Jolly要和设备树节点zane(我自己命的名)里的compatible一致才能匹配上//后面的leddrv是模块名,注意:和driver去match的不是模块名{ },
};static struct platform_driver chip_demo_gpio_driver = {.probe      = chip_demo_gpio_probe,.remove     = chip_demo_gpio_remove,.driver     = {.name   = "Jolly_led",//没有设备树的时候通过这个name去跟platform_device的name匹配//of_device_id是一个表,把这个表的指针赋给这个of_match_table就完成了匹配表的设定:.of_match_table = Zane_leds;},
};

我的设备树节点
在这里插入图片描述

向内核注册平台驱动(struct platform_driver),提供操作设备的函数(如初始化、探测、移除等)。

static int __init chip_demo_gpio_drv_init(void)
{int err;err = platform_driver_register(&chip_demo_gpio_driver); register_led_operations(&jolly_led_demo_opr);return 0;
}static void __exit lchip_demo_gpio_drv_exit(void)
{platform_driver_unregister(&chip_demo_gpio_driver);
}

当注册了platfrom_device 或 platfrom_drive 都会去按照这个规则来匹配对方

  • platform_device的.driver_override 与 platform_driver.driver中的.name;
  • platform_device的.of_node数组的某个.name与 与 platform_driver.driver.of_match_table数组的某个.name(来源于设备树);
  • platform_device的.name 与 platform_driver.id_table数组中的某个.name;(常用)
  • platform_deivce的.name 与 platform_driver.driver中的.name;(常用)

platfrom_device 和 platfrom_drive 匹配成功

当设备的device 和其对应的driver 在总线上完成配对之后,系统就调用 platform设备的probe函数完成驱动注册最后工作。

static int chip_demo_gpio_probe(struct platform_device *pdev)
{/*platform_get_resource 跟设备树没什么关系,但设备树的节点被转换为platform_device后,设备树中的reg属性、interrupts属性也会被转换为“resource”。*///这一段不是从设备树加载设备节点/*struct resource *res;int i = 0;while (1){//通过platform_get_resource在设备树一个个匹配platform_device *pdevres = platform_get_resource(pdev, IORESOURCE_IRQ, i);if (!res)//如果是空则说明没有设备了,也就没有必要进行下面的create_device了break;g_ledpins[g_ledcnt] = res->start;//取出资源led_class_create_device(g_ledcnt);//在probe里得到一个led设备再创建一个节点i++;g_ledcnt++;}return 0;*///二进制格式dtb设备树文件需要先转化成设备节点device_node结构,然后再将device_node转换成平台设备platform_device。struct device_node *np;int err = 0;int led_pin;np = pdev->dev.of_node;//把设备树转化成设备节点device_node结构if (!np)return -1;//np:要读取的设备树节点,“pin”:要读取的属性的名字,读取到以后放到led_pinerr = of_property_read_u32(np, "pin", &led_pin);//用于读取只有一个整形值的属性g_ledpins[g_ledcnt] = led_pin;led_class_create_device(g_ledcnt);g_ledcnt++;return 0;}

相关文章:

  • 【图像处理基石】OpenCV中都有哪些图像增强的工具?
  • 跨平台多用户环境下PDF表单“序列号生成的服务器端方案“
  • 大语言模型上下文长度:发展历程、局限与技术突破
  • INA226 高侧/低侧测量、双向电流/功率监视器,具有I2C兼容接口
  • 数字格式化库 accounting.js的使用说明
  • 什么是时间戳?怎么获取?有什么用
  • Java求职面试:从基础到复杂场景的技术深度解析
  • 【android bluetooth 协议分析 01】【HCI 层介绍 6】【WriteLeHostSupport命令介绍】
  • JVM如何处理多线程内存抢占问题
  • 王者荣耀游戏测试场景题
  • 上位机知识篇---流式Web服务器模式的实现
  • 为什么需要加密机服务?
  • 大模型deepseek如何助力数据安全管理
  • 使用国内源加速Qt在线安装
  • C++笔试题(金山科技新未来训练营):
  • 基于CNN的猫狗识别(自定义CNN模型)
  • SpringBoot快速上手
  • Spring AI 从入门到精通
  • 07、基础入门-SpringBoot-自动配置特性
  • Python Logging 模块完全指南
  • 俄乌刚谈完美国便筹划与俄乌领导人通话,目的几何?
  • 2025吉林市马拉松开跑,用赛道绘制“博物馆之城”动感地图
  • 新时代,新方志:2025上海地方志论坛暨理论研讨会举办
  • 蒲慕明院士:未来数十年不是AI取代人,而是会用AI的人取代不会用的
  • 精品消费“精”在哪?多在体验上下功夫
  • 人民日报:从“轻微免罚”看涉企执法方式转变