[Linux Device Driver] 组合按键

本文详细解析了在SM6350平台、Android 10、kernel version: msm-4.19上,如何通过修改代码实现Power键与Volume键组合的不同功能,包括进入recovery模式、Fastboot模式和edl模式。关键在于理解Bootloader中的按键处理函数和EFI_KEY的ScanCode映射,通过分析ButtonsLib.c和ButtonsDxe.c等源码,揭示了按键组合如何转化为系统响应的操作模式。

0. 背景

本文我已分享过同事,现在也分享给大家。

平台: SM6350、android 10、kernel version:msm-4.19

1. 代码

开机组合键需实现3个功能:

1. Power + Volume Up进入recovery模式

2. Power + Volume Down进入Fastboot模式

3. Power + Volume Up + Volume Down进入edl模式

在这里插入图片描述

2. 为啥这样改

这个是根据打印的log,然后改的,可能有的平台代码默认是好的。

代码路径:

bootable/bootloader/edk2/QcomModulePkg/Application/LinuxLoader/LinuxLoader.c

在这里插入图片描述
音量+ 和power键一起按,keyPressed 返回值是5(get getcode=5),串口log如下;

VB: Non-secure device: Security State: (0xF7F)
VB: RWDeviceState: Succeed using devinfo!
get getcode=5
grep -nr "SCAN_HOME" ./bootable/

在这里插入图片描述

检索SCAN_HOME的值是5,回到之前的修改,这样改会正确进入recovery模式。BootIntoRecovery = TRUE;

在这里插入图片描述

音量- 和power键一起按,keyPressed 返回值是8(get getcode=8),串口log如下:

VB: Non-secure device: Security State: (0xF7F)
VB: RWDeviceState: Succeed using devinfo!
get getcode=8
Total DDR Size: 0x00000000FDA00000
Fastboot=1, Recovery:0
grep -nr "SCAN_SCAN_DELETE" ./bootable/

在这里插入图片描述

SCAN_SCAN_DELETE这个定义的值就是8。

顺便看下三个键进入强制下载(音量+、音量-、power键一起按)的log。

UEFI Total : 1051 ms
POST Time      [ 2499] OS Loader
Loader Build Info: Oct 15 2020 21:03:37
VB: Non-secure device: Security State: (0xF7F)
VB: RWDeviceState: Succeed using devinfo!
get getcode=23
Start EBS        [ 2549]

get getcode=23,SCAN_ESC定义的值就是23。

3. 拓展

我比较关心音量+ 和power键一起按,怎么就对应SCAN_HOME了,SCAN_SCAN_DELETE
还有SCAN_ESC是如何对应的。于是我去modem那边检索看看。

bitra-la-1-0_amss_standard_oem$ grep -nr "SCAN_DELETE"  ./

终端返回结果如下:

在这里插入图片描述

vim ./BOOT.XF.3.3/boot_images/ShellPkg/Application/Shell/FileHandleWrappers.c +607

我先看了下第一个文件,淦,没看到啥,里面有些command的东西不知道干啥的…,然后想到这个不是几个按键吗,于是看看这个文件:ButtonsLib.c

vim ./BOOT.XF.3.3/boot_images/QcomPkg/SocPkg/BitraPkg/Library/ButtonsLib/ButtonsLib.c

在这里插入图片描述

这个代码里面翻看了下,发现了power key、volume up、volume down这些东西,这个代码的意思是检查某个按键被按下(ButtonPressed),然后把值存到pButtonArray数组里面。

然后检查 看看谁调用了这个函数:PollButtonArray

grep -nr "PollButtonArray" ./

在这里插入图片描述

从console返回的信息来看,只找到一个地方, 于是打开看看。

vim ./BOOT.XF.3.3/boot_images/QcomPkg/Drivers/ButtonsDxe/ButtonsDxe.c +361

在这里插入图片描述

这段代码的大概意思是处理按键事件(调用PollButtonArray函数)。然后通过
ConvertEfiKeyCode函数作了某种转换,把值存到了key buffer里面。

我们继续查找下ConvertEfiKeyCode函数原型,看它搞啥鬼。

grep -nr "ConvertEfiKeyCode"  ./BOOT.XF.3.3/boot_images/

在这里插入图片描述

./BOOT.XF.3.3/boot_images/QcomPkg/Include/Library/KeypadLib.h
./BOOT.XF.3.3/boot_images/QcomPkg/Include/Library/ButtonsLib.h

在这里插入图片描述

Vim ./BOOT.XF.3.3/boot_images/QcomPkg/SocPkg/BitraPkg/Library/ButtonsLib/ButtonsLib.c +146/

进入这里发现核心代码就在里面。

EFI_STATUS ConvertEfiKeyCode (
   KEY_TYPE       *pKeysPressed,
   UINT8           numOfKeysPressed,
   KEY_TYPE       *pKeysReleased,
   UINT8           numOfKeysReleased,
   UINT8           sizeOfPressedReleasedArray,
   EFI_INPUT_KEY  *pEfiKeys,
   UINT8          *pNumOfEfiKeys
   )
{
   EFI_STATUS retVal = EFI_NOT_FOUND;
   EFI_INPUT_KEY EfiKey;

   EfiKey.ScanCode = SCAN_NULL;
   EfiKey.UnicodeChar = 0;
   *pNumOfEfiKeys = 0;
   isHomeKeyDetected = FALSE;

   BOOLEAN bCameraKeyIsPressed;
   BOOLEAN bVolUpKeyIsPressed;
   BOOLEAN bVolDownKeyIsPressed;
   BOOLEAN bCameraKeyIsReleased;
   BOOLEAN bHomeKeyIsPressed;
   BOOLEAN bPwrKeyPressed;

   if( isEfiKeyDetected == FALSE )
   {
      bCameraKeyIsPressed    = FoundAKey(pKeysPressed, sizeOfPressedReleasedArray, CAMERA);
      bVolUpKeyIsPressed     = FoundAKey(pKeysPressed, sizeOfPressedReleasedArray, VOL_UP);
      bVolDownKeyIsPressed   = FoundAKey(pKeysPressed, sizeOfPressedReleasedArray, VOL_DOWN);
      bCameraKeyIsReleased   = FoundAKey(pKeysReleased,sizeOfPressedReleasedArray, CAMERA);
      bHomeKeyIsPressed      = FoundAKey(pKeysPressed, sizeOfPressedReleasedArray, HOME);
      bPwrKeyPressed         = FoundAKey(pKeysPressed, sizeOfPressedReleasedArray, PWR);

      // assume EFI key found
      isEfiKeyDetected = TRUE;

      if( bVolUpKeyIsPressed && bVolDownKeyIsPressed )
      {
         // combo key found
         EfiKey.ScanCode = SCAN_ESC;
      }
      else if( bCameraKeyIsPressed && bVolUpKeyIsPressed )
      {
         // combo key found
         EfiKey.ScanCode = SCAN_HOME;
      }
      else if( bCameraKeyIsPressed && bVolDownKeyIsPressed )
      {
         // combo key found
         EfiKey.ScanCode = SCAN_DELETE;
      }
      else if( bVolUpKeyIsPressed )
      {
         if( bPwrKeyPressed )
         {
            // combo key found
            EfiKey.ScanCode = SCAN_HOME;
         }
         else
         {
            // single key found
            EfiKey.ScanCode = SCAN_UP;
         }
      }
      else if( bVolDownKeyIsPressed )
      {
         if( bPwrKeyPressed )
         {
            // combo key found
            EfiKey.ScanCode = SCAN_DELETE;
         }
         else
         ......

回到我们最开始的patch与要求:

在这里插入图片描述

在这里插入图片描述
Volume Down + Power是SCAN_DELETE,所以进入fastboot;

在这里插入图片描述

Volume Up + Power是SCAN_HOME,所以进入recovery;

在这里插入图片描述

Volume Up和Volume Down一起按的时候是SCAN_ESC,所以进入强制下载edl。

<think> 嗯,用户想了解Linux设备驱动分离的原理、方法和应用。根据引用资料,设备驱动与硬件紧耦合会导致系统开发和维护困难,Linux引入了设备与驱动分离的思想。引用提到设备树是实现这种分离的关键技术,它作为描述硬件配置的标准方式,提高了系统可移植性和可维护性。 用户可能是个嵌入式开发者或内核驱动学习者,需要深入理解驱动架构设计理念。从引用看,用户已经接触过设备树、Pinctrl/GPIO子系统等概念,但需要系统性的梳理。尤其引用中提到的设备号管理和资源优化,暗示用户可能还关注实际开发细节。 设备驱动分离的核心原理在于解耦硬件描述和驱动逻辑。具体方法上,设备树(.dts文件)承担硬件描述的职责,比如GPIO引脚、寄存器地址等;而驱动代码(.c文件)专注于功能实现。两者通过OF(Open Firmware)机制匹配,驱动会调用of_match_table比对设备树中的compatible属性。 应用层面特别值得注意的是:1)新硬件支持只需修改设备树无需重新编译内核;2)同个驱动可适配不同硬件配置;3)如引用所述,结合platform虚拟总线管理资源更高效。用户提到的字符设备驱动案例中,register_chrdev_region()能优化设备号分配,正是资源管理的体现。 用户可能还想知道:这种分离模式对启动流程的影响?用户空间如何访问分离后的设备?不过当前问题聚焦在原理和应用,先解决基础框架更合适。补充引用中宋宝华教材的案例会更直观——驱动注册时提供.probe回调,设备树节点被解析时会触发该函数,实现"动态装配"。 </think> Linux系统中设备与驱动分离的设计是解决硬件描述与驱动代码强耦合问题的核心机制,其原理、方法及应用如下: --- ### **一、分离原理** 1. **解耦思想** 传统驱动中硬件参数(如寄存器地址、中断号)直接写死在驱动代码中,导致: - 同一驱动无法适配不同硬件配置 - 内核需为每个硬件重新编译 - 维护困难(如引用[1]所述 *"紧耦合设计使驱动与系统难以分离"*) **分离方案**: - **设备层**:通过设备树(Device Tree)或ACPI描述硬件资源 - **驱动层**:代码仅关注功能逻辑,运行时动态匹配设备描述 2. **匹配机制** - **设备树节点**:定义`compatible`属性(如`"vendor,device-id"`) - **驱动代码**:声明相同`compatible`字符串的`of_match_table` - **总线核心**:启动时根据`compatible`值绑定设备与驱动(如引用[2]强调 *"设备树将硬件描述与内核分离"*) --- ### **二、实现方法** #### **1. 设备描述(硬件侧)** ```dts // 设备树示例 (e.g., arch/arm/boot/dts/myboard.dts) leds { compatible = "myvendor,board-leds"; // 唯一标识符 led-gpio = <&gpio0 12 GPIO_ACTIVE_HIGH>; // 硬件资源定义 status = "okay"; }; ``` #### **2. 驱动开发(软件侧)** ```c // 驱动代码 (e.g., drivers/leds/leds-myvendor.c) static const struct of_device_id leds_match_table[] = { { .compatible = "myvendor,board-leds" }, // 匹配设备树节点 {} }; static struct platform_driver myvendor_led_driver = { .probe = led_probe, // 设备匹配时调用 .driver = { .name = "myvendor-led", .of_match_table = leds_match_table, // 关联匹配表 }, }; module_platform_driver(myvendor_led_driver); ``` #### **3. 关键过程** - **资源获取**: 在`probe()`函数中解析设备树资源: ```c int led_probe(struct platform_device *pdev) { struct gpio_desc *led_gpio; led_gpio = gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW); // 通过名称获取GPIO // 注册字符设备等操作(如引用[4]提及的register_chrdev_region优化) } ``` - **分离优势**: 同一驱动兼容不同硬件: ```dts // 新设备只需修改设备树 leds-v2 { compatible = "myvendor,board-leds"; // 相同标识符 led-gpio = <&gpio1 15 GPIO_ACTIVE_LOW>; // GPIO改变 }; ``` --- ### **三、应用价值** 1. **可移植性提升** - ARM开发板更换外设时,仅需调整设备树,内核无需重新编译(如引用[2]强调 *"提高可移植性"*)。 2. **驱动通用化** - 同一驱动支持多平台(如GPIO按键驱动可适配树莓派、i.MX6等)。 3. **动态配置** - 通过Device Tree Overlays实时加载/卸载硬件配置。 4. **资源管理优化** - 自动分配设备号(`alloc_chrdev_region`),避免静态分配冲突(如引用[4]对比`register_chrdev`与`register_chrdev_region`)。 5. **子系统集成** - 与**Pinctrl/GPIO子系统**协同:自动配置引脚复用状态 - 与**Platform总线**结合:管理虚拟或实体设备 --- ### **四、典型场景** - **嵌入式Linux开发**:不同电路板复用同一内核镜像 - **热插拔设备**:USB设备插入时动态加载驱动 - **异构计算**:GPU、NPU等协处理器的灵活描述 > 此设计显著降低了驱动开发复杂度,正如设备树的核心目标:**将硬件的可变性与驱动代码的稳定性解耦**[^2][^4]。 ---
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值