PX4/Pixhawk 教程 - 任务线程 - workqueue 和 task

介绍

一个完整的px4的应用程序(或者叫任务)分为前台部分和后台部分,前台部分是跑在shell任务中的,比如helloworld那个程序就只有前台部分,敲入指令即可在ssh中运行,前台部分一般是用来测试或者启动后台任务。

px4的后台任务主要油两种,一种叫任务(Tasks),一种叫工作队列(Work queue tasks)。两者的主要区别是任务拥有独立的栈空间和进程优先级,而工作队列是跑在一个统一的进程里的多个线程,共享优先级、栈空间。

优缺点对比

任务:优点是可以保证优先级的切换;可以进行阻塞操作;

工作队列:优点是消耗更少的RAM和cpu(因为避免了任务切换);时基实时性好,支持定时任务,适合周期性的传感器数据读取;

时基实时性,比如一个任务需要在t1,t2,t3,t4时刻进行操作,有的任务希望保证t2-t1,t3-t2,t4-t3这种相对时间间隔的准确性,这种场景任务更适合;而有的任务希望保证t2-t1,t3-t1,t4-t1这种相对于初始时刻的绝对时间的准确性,这种场景下工作队列更适合。

任务(Tasks)

任务的特点:

  • 拥有独立的栈空间;
  • 低优先级的任务会被高优先级的任务打断;
  • 允许使用sleep函数和进行阻塞操作;

相关函数、命令介绍

  1. 新建一个任务:
independent_task = px4_task_spawn_cmd
int mt_accdet_probe(struct platform_device dev) 1632 { 1633 int ret = 0; 1634 1635 #if DEBUG_THREAD 1636 struct platform_driver accdet_driver_hal = accdet_driver_func(); 1637 #endif 1638 1639 #if defined(CONFIG_TS3A225E_ACCDET) 1640 if (ts3a225e_i2c_client == NULL) { 1641 ACCDET_DEBUG(“[Accdet]ts3a225e_i2c_client is NULL!\n”); 1642 return -EPROBE_DEFER; 1643 } 1644 #endif 1645 1646 ACCDET_INFO(“[Accdet]accdet_probe begin!\n”); 1647 1648 /-------------------------------------------------------------------- 1649 // below register accdet as switch class 1650 //------------------------------------------------------------------/ 1651 #if JUST_INPUT_NO_SWITCH 1652 accdet_data.name = “h2w”; 1653 accdet_data.index = 0; 1654 accdet_data.state = NO_DEVICE; 1655 ret = switch_dev_register(&accdet_data); 1656 if (ret) { 1657 ACCDET_ERROR(“[Accdet]switch_dev_register returned:%d!\n”, ret); 1658 return 1; 1659 } 1660 #endif 1661 /---------------------------------------------------------------------- 1662 // Create normal device for auido use 1663 //--------------------------------------------------------------------/ 1664 ret = alloc_chrdev_region(&accdet_devno, 0, 1, ACCDET_DEVNAME); 1665 if (ret) 1666 ACCDET_ERROR(“[Accdet]alloc_chrdev_region: Get Major number error!\n”); 1667 1668 accdet_cdev = cdev_alloc(); 1669 accdet_cdev->owner = THIS_MODULE; 1670 accdet_cdev->ops = accdet_get_fops(); 1671 ret = cdev_add(accdet_cdev, accdet_devno, 1); 1672 if (ret) 1673 ACCDET_ERROR(“[Accdet]accdet error: cdev_add\n”); 1674 1675 accdet_class = class_create(THIS_MODULE, ACCDET_DEVNAME); 1676 1677 / if we want auto creat device node, we must call this*/ 1678 accdet_nor_device = device_create(accdet_class, NULL, accdet_devno, NULL, ACCDET_DEVNAME); 1679 1680 /-------------------------------------------------------------------- 1681 // Create input device 1682 //------------------------------------------------------------------/ 1683 kpd_accdet_dev = input_allocate_device(); 1684 if (!kpd_accdet_dev) { 1685 ACCDET_ERROR(“[Accdet]kpd_accdet_dev : fail!\n”); 1686 return -ENOMEM; 1687 } 1688 /INIT the timer to disable micbias./ 1689 timer_setup(&micbias_timer, disable_micbias, 0); 1690 micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER; 1691 1692 /define multi-key keycode/ 1693 __set_bit(EV_KEY, kpd_accdet_dev->evbit); 1694 __set_bit(KEY_PLAYPAUSE, kpd_accdet_dev->keybit); 1695 __set_bit(KEY_VOLUMEDOWN, kpd_accdet_dev->keybit); 1696 __set_bit(KEY_VOLUMEUP, kpd_accdet_dev->keybit); 1697 __set_bit(KEY_VOICECOMMAND, kpd_accdet_dev->keybit); 1698 1699 __set_bit(EV_SW, kpd_accdet_dev->evbit); 1700 __set_bit(SW_HEADPHONE_INSERT, kpd_accdet_dev->swbit); 1701 __set_bit(SW_MICROPHONE_INSERT, kpd_accdet_dev->swbit); 1702 __set_bit(SW_JACK_PHYSICAL_INSERT, kpd_accdet_dev->swbit); 1703 __set_bit(SW_LINEOUT_INSERT, kpd_accdet_dev->swbit); 1704 1705 kpd_accdet_dev->id.bustype = BUS_HOST; 1706 kpd_accdet_dev->name = “ACCDET”; 1707 if (input_register_device(kpd_accdet_dev)) 1708 ACCDET_ERROR(“[Accdet]kpd_accdet_dev register : fail!\n”); 1709 /------------------------------------------------------------------ 1710 // Create workqueue 1711 //------------------------------------------------------------------ / 1712 accdet_workqueue = create_singlethread_workqueue(“accdet”); 1713 INIT_WORK(&accdet_work, accdet_work_callback); 1714 1715 /------------------------------------------------------------------ 1716 // wake lock 1717 //------------------------------------------------------------------/ 1718 accdet_suspend_lock = wakeup_source_register(NULL, “accdet_suspend_lock”); 1719 accdet_irq_lock = wakeup_source_register(NULL, “accdet_irq_lock”); 1720 accdet_key_lock = wakeup_source_register(NULL, “accdet_key_lock”); 1721 accdet_timer_lock = wakeup_source_register(NULL, “accdet_timer_lock”); 1722 #if DEBUG_THREAD 1723 ret = accdet_create_attr(&accdet_driver_hal.driver); 1724 if (ret != 0) 1725 ACCDET_ERROR(“create attribute err = %d\n”, ret); 1726 #endif 1727 pmic_register_interrupt_callback(12, accdet_int_handler); 1728 pmic_register_interrupt_callback(13, accdet_eint_int_handler); 1729 ACCDET_INFO(“[Accdet]accdet_probe : ACCDET_INIT\n”); 1730 if (g_accdet_first == 1) { 1731 eint_accdet_sync_flag = 1; 1732 #ifdef CONFIG_ACCDET_EINT_IRQ 1733 accdet_eint_workqueue = create_singlethread_workqueue(“accdet_eint”); 1734 INIT_WORK(&accdet_eint_work, accdet_eint_work_callback); 1735 accdet_disable_workqueue = create_singlethread_workqueue(“accdet_disable”); 1736 INIT_WORK(&accdet_disable_work, disable_micbias_callback); 1737 #endif 1738 /Accdet Hardware Init/ 1739 accdet_get_dts_data(); 1740 accdet_init(); 1741 accdet_pmic_Read_Efuse_HPOffset(); 1742 /schedule a work for the first detection/ 1743 queue_work(accdet_workqueue, &accdet_work); 1744 #ifdef CONFIG_ACCDET_EINT 1745 accdet_disable_workqueue = create_singlethread_workqueue(“accdet_disable”); 1746 INIT_WORK(&accdet_disable_work, disable_micbias_callback); 1747 accdet_eint_workqueue = create_singlethread_workqueue(“accdet_eint”); 1748 INIT_WORK(&accdet_eint_work, accdet_eint_work_callback); 1749 accdet_setup_eint(dev); 1750 #endif 1751 g_accdet_first = 0; 1752 } 1753 ACCDET_INFO(“[Accdet]accdet_probe done!\n”); 1754 return 0; 1755 }
最新发布
07-15
<think>我们正在分析 `mt_accdet_probe` 函数,这是耳机检测(Accdet)驱动的平台设备探测函数。该函数负责初始化驱动,包括注册设备、创建输入设备、工作队列、唤醒锁以及设置中断处理等。 ### 函数流程解析 #### 1. **条件编译与初始化检查** ```c #if defined(CONFIG_TS3A225E_ACCDET) if (ts3a225e_i2c_client == NULL) { ACCDET_DEBUG("[Accdet]ts3a225e_i2c_client is NULL!\n"); return -EPROBE_DEFER; } #endif ``` - 如果配置了 `CONFIG_TS3A225E_ACCDET`(使用TI的耳机检测芯片),则检查I2C客户端是否初始化。若为空,返回 `-EPROBE_DEFER` 表示稍后重试。 #### 2. **注册Switch设备(条件编译)** ```c #if JUST_INPUT_NO_SWITCH accdet_data.name = "h2w"; accdet_data.index = 0; accdet_data.state = NO_DEVICE; ret = switch_dev_register(&accdet_data); if (ret) { ACCDET_ERROR("[Accdet]switch_dev_register returned:%d!\n", ret); return 1; } #endif ``` - 当启用 `JUST_INPUT_NO_SWITCH` 时,注册一个Switch设备(名为"h2w"),用于报告耳机插拔状态。 #### 3. **注册字符设备** ```c ret = alloc_chrdev_region(&accdet_devno, 0, 1, ACCDET_DEVNAME); if (ret) ACCDET_ERROR("[Accdet]alloc_chrdev_region: Get Major number error!\n"); accdet_cdev = cdev_alloc(); accdet_cdev->owner = THIS_MODULE; accdet_cdev->ops = accdet_get_fops(); ret = cdev_add(accdet_cdev, accdet_devno, 1); if (ret) ACCDET_ERROR("[Accdet]accdet error: cdev_add\n"); accdet_class = class_create(THIS_MODULE, ACCDET_DEVNAME); accdet_nor_device = device_create(accdet_class, NULL, accdet_devno, NULL, ACCDET_DEVNAME); ``` - 为音频相关操作注册字符设备(如 `ioctl` 控制)。步骤包括: 1. 分配设备号。 2. 分配并初始化 `cdev` 结构,设置文件操作函数。 3. 添加字符设备到内核。 4. 创建设备类设备节点。 #### 4. **创建输入设备** ```c kpd_accdet_dev = input_allocate_device(); if (!kpd_accdet_dev) { ACCDET_ERROR("[Accdet]kpd_accdet_dev : fail!\n"); return -ENOMEM; } // 初始化定时器(用于关闭麦克风偏置电压) timer_setup(&micbias_timer, disable_micbias, 0); micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER; // 设置按键事件 __set_bit(EV_KEY, kpd_accdet_dev->evbit); __set_bit(KEY_PLAYPAUSE, kpd_accdet_dev->keybit); __set_bit(KEY_VOLUMEDOWN, kpd_accdet_dev->keybit); __set_bit(KEY_VOLUMEUP, kpd_accdet_dev->keybit); __set_bit(KEY_VOICECOMMAND, kpd_accdet_dev->keybit); // 设置开关事件(耳机/麦克风插入) __set_bit(EV_SW, kpd_accdet_dev->evbit); __set_bit(SW_HEADPHONE_INSERT, kpd_accdet_dev->swbit); __set_bit(SW_MICROPHONE_INSERT, kpd_accdet_dev->swbit); __set_bit(SW_JACK_PHYSICAL_INSERT, kpd_accdet_dev->swbit); __set_bit(SW_LINEOUT_INSERT, kpd_accdet_dev->swbit); kpd_accdet_dev->id.bustype = BUS_HOST; kpd_accdet_dev->name = "ACCDET"; if (input_register_device(kpd_accdet_dev)) ACCDET_ERROR("[Accdet]kpd_accdet_dev register : fail!\n"); ``` - 分配并初始化输入设备: - 设置按键事件:播放/暂停、音量加减、语音命令。 - 设置开关事件:耳机插入、麦克风插入、物理插孔插入、线输出插入。 - 注册输入设备。 #### 5. **创建工作队列** ```c accdet_workqueue = create_singlethread_workqueue("accdet"); INIT_WORK(&accdet_work, accdet_work_callback); ``` - 创建单线程工作队列 `accdet`,并初始化工作项 `accdet_work`,其回调函数为 `accdet_work_callback`(用于处理耳机检测事件)。 #### 6. **注册唤醒锁** ```c accdet_suspend_lock = wakeup_source_register(NULL, "accdet_suspend_lock"); accdet_irq_lock = wakeup_source_register(NULL, "accdet_irq_lock"); accdet_key_lock = wakeup_source_register(NULL, "accdet_key_lock"); accdet_timer_lock = wakeup_source_register(NULL, "accdet_timer_lock"); ``` - 注册多个唤醒锁,用于在关键操作期间阻止系统休眠。 #### 7. **注册中断处理函数** ```c pmic_register_interrupt_callback(12, accdet_int_handler); pmic_register_interrupt_callback(13, accdet_eint_int_handler); ``` - 注册PMIC中断回调: - 中断12:用于耳机插入检测。 - 中断13:用于外部中断(EINT)检测。 #### 8. **首次初始化(`g_accdet_first` 控制)** ```c if (g_accdet_first == 1) { eint_accdet_sync_flag = 1; #ifdef CONFIG_ACCDET_EINT_IRQ accdet_eint_workqueue = create_singlethread_workqueue("accdet_eint"); INIT_WORK(&accdet_eint_work, accdet_eint_work_callback); accdet_disable_workqueue = create_singlethread_workqueue("accdet_disable"); INIT_WORK(&accdet_disable_work, disable_micbias_callback); #endif // 从设备树获取数据 accdet_get_dts_data(); // 初始化硬件 accdet_init(); // 读取EFUSE中的耳机偏移值 accdet_pmic_Read_Efuse_HPOffset(); // 调度工作队列进行首次检测 queue_work(accdet_workqueue, &accdet_work); #ifdef CONFIG_ACCDET_EINT // 重复创建?可能是为了不同配置条件 accdet_disable_workqueue = create_singlethread_workqueue("accdet_disable"); INIT_WORK(&accdet_disable_work, disable_micbias_callback); accdet_eint_workqueue = create_singlethread_workqueue("accdet_eint"); INIT_WORK(&accdet_eint_work, accdet_eint_work_callback); // 设置EINT accdet_setup_eint(dev); #endif g_accdet_first = 0; } ``` - 只在首次探测时执行: 1. 设置同步标志 `eint_accdet_sync_flag=1`(允许事件上报)。 2. 若配置了外部中断(`CONFIG_ACCDET_EINT_IRQ` 或 `CONFIG_ACCDET_EINT`),创建工作队列并初始化工作项。 3. 调用 `accdet_get_dts_data` 从设备树读取参数。 4. 硬件初始化 `accdet_init()`。 5. 读取EFUSE中的耳机偏移值(用于校准)。 6. 调度工作队列进行首次耳机检测(模拟插入事件)。 7. 设置外部中断(`accdet_setup_eint`)。 8. 标记初始化完成(`g_accdet_first=0`)。 ### 关键点总结 - **多设备支持**:通过字符设备、输入设备、Switch设备提供多种接口。 - **工作队列**:处理耗时操作(如硬件检测),避免在中断上下文中执行。 - **唤醒锁**:确保关键操作期间系统不会休眠。 - **首次检测**:在驱动加载时主动检测当前耳机状态。 - **条件编译**:根据内核配置支持不同硬件(如TI芯片、外部中断等)。 §§相关问题§§ 1. 为什么在注册PMIC中断(1213)时不检查返回值?如果注册失败会导致什么问题? 2. 当 `g_accdet_first` 为0时(非首次初始化),哪些关键初始化步骤会被跳过?这是否会导致功能异常? 3. 在输入设备中同时设置 `EV_KEY` `EV_SW` 事件的作用是什么?用户空间如何区分这些事件? 4. 函数中两次创建同名工作队列(如 `accdet_disable_workqueue`)的条件编译块(`CONFIG_ACCDET_EINT_IRQ` `CONFIG_ACCDET_EINT`)是否会导致资源重复分配? 5. 定时器 `micbias_timer` 的作用是延迟关闭麦克风偏置电压,其超时时间 `MICBIAS_DISABLE_TIMER` 的单位是什么?如何计算实际延迟时间?</think>### `mt_accdet_probe` 函数深度解析 此函数是耳机检测(Accdet)驱动的**核心初始化函数**,负责设备注册、资源分配硬件初始化。以下是关键流程的详细分析: #### 1. **外部芯片检测(条件编译)** ```c #if defined(CONFIG_TS3A225E_ACCDET) if (ts3a225e_i2c_client == NULL) { return -EPROBE_DEFER; // 延迟探测 } #endif ``` - **作用**:检测外部耳机芯片(如TI的TS3A225E)是否就绪,未就绪时返回 `-EPROBE_DEFER` 让内核稍后重试。 --- #### 2. **设备注册三重机制** ##### (1) Switch设备(用户空间状态通知) ```c #if JUST_INPUT_NO_SWITCH accdet_data.name = "h2w"; switch_dev_register(&accdet_data); // 注册到/sys/class/switch/h2w #endif ``` - 提供标准接口供Android框架监听耳机状态变化。 ##### (2) 字符设备(控制接口) ```c alloc_chrdev_region(&accdet_devno, 0, 1, ACCDET_DEVNAME); // 分配设备号 cdev_add(accdet_cdev, accdet_devno, 1); // 注册字符设备 device_create(accdet_class, NULL, accdet_devno, NULL, ACCDET_DEVNAME); // 创建设备节点 ``` - 创建 `/dev/accdet` 节点,支持 `ioctl` 等控制操作。 ##### (3) 输入设备(事件上报) ```c kpd_accdet_dev = input_allocate_device(); // 设置按键事件 __set_bit(KEY_PLAYPAUSE, kpd_accdet_dev->keybit); __set_bit(KEY_VOLUMEDOWN, kpd_accdet_dev->keybit); // 设置开关事件 __set_bit(SW_HEADPHONE_INSERT, kpd_accdet_dev->swbit); __set_bit(SW_MICROPHONE_INSERT, kpd_accdet_dev->swbit); input_register_device(kpd_accdet_dev); // 注册输入设备 ``` - **事件能力**: - `EV_KEY`:媒体按键(播放/暂停、音量控制) - `EV_SW`:插入状态(耳机/麦克风/线缆) - 设备名称为 `"ACCDET"`,通过 `/dev/input/eventX` 上报事件。 --- #### 3. **核心资源初始化** ##### (1) 工作队列 ```c accdet_workqueue = create_singlethread_workqueue("accdet"); // 单线程工作队列 INIT_WORK(&accdet_work, accdet_work_callback); // 绑定工作回调 ``` - 创建专用工作队列处理耳机检测任务。 ##### (2) 唤醒锁(Power Management) ```c accdet_suspend_lock = wakeup_source_register(NULL, "accdet_suspend_lock"); accdet_irq_lock = wakeup_source_register(NULL, "accdet_irq_lock"); accdet_key_lock = wakeup_source_register(NULL, "accdet_key_lock"); ``` - 四种唤醒锁: - `suspend_lock`:阻止系统休眠 - `irq_lock`:中断处理期间保持唤醒 - `key_lock`:按键事件处理期间保持唤醒 - `timer_lock`:定时器运行时保持唤醒 ##### (3) 定时器(麦克风偏置控制) ```c timer_setup(&micbias_timer, disable_micbias, 0); // 初始化定时器 micbias_timer.expires = jiffies + MICBIAS_DISABLE_TIMER; // 设置超时时间 ``` - 用于延迟关闭麦克风偏置电压,节省功耗。 --- #### 4. **中断与首次初始化** ##### (1) 中断注册 ```c pmic_register_interrupt_callback(12, accdet_int_handler); // 中断12:耳机检测 pmic_register_interrupt_callback(13, accdet_eint_int_handler); // 中断13:外部中断 ``` - 绑定硬件中断到处理函数。 ##### (2) 首次初始化流程 ```c if (g_accdet_first == 1) { // 仅首次执行 eint_accdet_sync_flag = 1; // 允许事件上报 accdet_get_dts_data(); // 从设备树加载参数 accdet_init(); // 硬件初始化 accdet_pmic_Read_Efuse_HPOffset(); // 读取校准值 queue_work(accdet_workqueue, &accdet_work); // 触发首次检测 #ifdef CONFIG_ACCDET_EINT accdet_setup_eint(dev); // 配置外部中断 #endif g_accdet_first = 0; // 标记初始化完成 } ``` - **关键操作**: 1. 读取设备树参数(防抖时间、按键阈值等) 2. 初始化硬件寄存器 3. 读取EFUSE中的耳机偏移校准值 4. 主动触发首次耳机检测(模拟插入事件) 5. 配置外部中断引脚(如耳机插孔检测) --- ### 函数执行流程总结 ```mermaid graph TD A[开始探测] --> B{外部芯片就绪?} B ----> C[注册Switch设备] C --> D[注册字符设备] D --> E[注册输入设备] E --> F[创建工作队列] F --> G[注册唤醒锁] G --> H[设置麦克风定时器] H --> I[注册PMIC中断] I --> J{首次初始化?} J ----> K[加载设备树参数] K --> L[初始化硬件] L --> M[读取校准值] M --> N[触发首次检测] N --> O[配置外部中断] O --> P[标记初始化完成] ``` ### 设计要点 1. **状态隔离**:通过 `g_accdet_first` 确保硬件初始化仅执行一次 2. **事件分层**: - 输入设备 → 原始事件上报 - Switch设备 → 高层状态抽象 - 字符设备 → 控制接口 3. **功耗优化**: - 唤醒锁按场景细分(4种类型) - 麦克风偏置电压延迟关闭 4. **健壮性**: - 外部芯片未就绪时延迟探测(`-EPROBE_DEFER`) - 首次检测主动同步硬件状态
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值