• Linux内核中ideapad-laptop.c文件全解析9


    接前一篇文章《Linux内核中ideapad-laptop.c文件全解析8》,地址为:

    Linux内核中ideapad-laptop.c文件全解析8_蓝天居士的博客-CSDN博客

    上一回详细分析了ideapad_kbd_bl_init,这一回详细分析ideapad_acpi_notify。

    • ideapad_acpi_notify

    ideapad_acpi_notify函数在同文件(linux-5.18.8/drivers/platform/x86/ideapad-laptop.c)中实现,源码如下:

    1. static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
    2. {
    3. struct ideapad_private *priv = data;
    4. unsigned long vpc1, vpc2, bit;
    5. if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
    6. return;
    7. if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
    8. return;
    9. vpc1 = (vpc2 << 8) | vpc1;
    10. for_each_set_bit (bit, &vpc1, 16) {
    11. switch (bit) {
    12. case 13:
    13. case 11:
    14. case 8:
    15. case 7:
    16. case 6:
    17. ideapad_input_report(priv, bit);
    18. break;
    19. case 10:
    20. /*
    21. * This event gets send on a Yoga 300-11IBR when the EC
    22. * believes that the device has changed between laptop/
    23. * tent/stand/tablet mode. The EC relies on getting
    24. * angle info from 2 accelerometers through a special
    25. * windows service calling a DSM on the DUAL250E ACPI-
    26. * device. Linux does not do this, making the laptop/
    27. * tent/stand/tablet mode info unreliable, so we simply
    28. * ignore these events.
    29. */
    30. break;
    31. case 9:
    32. ideapad_sync_rfk_state(priv);
    33. break;
    34. case 5:
    35. ideapad_sync_touchpad_state(priv);
    36. break;
    37. case 4:
    38. ideapad_backlight_notify_brightness(priv);
    39. break;
    40. case 3:
    41. ideapad_input_novokey(priv);
    42. break;
    43. case 2:
    44. ideapad_backlight_notify_power(priv);
    45. break;
    46. case 1:
    47. /*
    48. * Some IdeaPads report event 1 every ~20
    49. * seconds while on battery power; some
    50. * report this when changing to/from tablet
    51. * mode; some report this when the keyboard
    52. * backlight has changed.
    53. */
    54. ideapad_kbd_bl_notify(priv);
    55. break;
    56. case 0:
    57. ideapad_check_special_buttons(priv);
    58. break;
    59. default:
    60. dev_info(&priv->platform_device->dev,
    61. "Unknown event: %lu\n", bit);
    62. }
    63. }
    64. }

    函数一上来通过read_ec_data读取ec中的寄存器值(VPCCMD_R_VPC1和VPCCMD_R_VPC2),之后将读到的两个值vpc1和vpc2组合在一起,赋给vpc1(vpc1 = (vpc2 << 8) | vpc1;)。

    然后依次遍历vpc1的低16位,略过没有被置位的位,而对于被置位的位进行相应的处理。这里我们重点关注case 1的处理,其中的处理函数为ideapad_kbd_bl_notify(priv)。

    这里看一下for_each_set_bit宏定义,在include/linux/find.h中:

    1. #define for_each_set_bit(bit, addr, size) \
    2. for ((bit) = find_next_bit((addr), (size), 0); \
    3. (bit) < (size); \
    4. (bit) = find_next_bit((addr), (size), (bit) + 1))

    find_next_bit在同文件中定义并实现,代码如下:

    1. #ifndef find_next_bit
    2. /**
    3. * find_next_bit - find the next set bit in a memory region
    4. * @addr: The address to base the search on
    5. * @offset: The bitnumber to start searching at
    6. * @size: The bitmap size in bits
    7. *
    8. * Returns the bit number for the next set bit
    9. * If no bits are set, returns @size.
    10. */
    11. static inline
    12. unsigned long find_next_bit(const unsigned long *addr, unsigned long size,
    13. unsigned long offset)
    14. {
    15. if (small_const_nbits(size)) {
    16. unsigned long val;
    17. if (unlikely(offset >= size))
    18. return size;
    19. val = *addr & GENMASK(size - 1, offset);
    20. return val ? __ffs(val) : size;
    21. }
    22. return _find_next_bit(addr, NULL, size, offset, 0UL, 0);
    23. }
    24. #endif

    下边还是分析ideapad_kbd_bl_notify函数,其在drivers/platform/x86/ideapad-laptop.c中,代码如下:

    1. static void ideapad_kbd_bl_notify(struct ideapad_private *priv)
    2. {
    3. int brightness;
    4. if (!priv->kbd_bl.initialized)
    5. return;
    6. brightness = ideapad_kbd_bl_brightness_get(priv);
    7. if (brightness < 0)
    8. return;
    9. if (brightness == priv->kbd_bl.last_brightness)
    10. return;
    11. priv->kbd_bl.last_brightness = brightness;
    12. led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness);
    13. }

    如果kbd_bl.initialized为false,即没有被初始化过,则直接返回。根据《Linux内核中ideapad-laptop.c文件全解析7》中的介绍,在ideapad_kbd_bl_init函数中,kbd_bl.initialized已经被设置为了true。

    ideapad_kbd_bl_brightness_get函数在同文件中实现,代码如下:

    1. static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
    2. {
    3. unsigned long hals;
    4. int err;
    5. err = eval_hals(priv->adev->handle, &hals);
    6. if (err)
    7. return err;
    8. return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals);
    9. }

    test_bit是一个架构相关函数,其作用是原子地返回addr所指对象的nr位。在这里是返回hals的HALS_KBD_BL_STATE_BIT位,给brightness。

    如果本次的brightness值不等于上一次的值,则把本次的值赋给上一次的值last_brightness。

    led_classdev_notify_brightness_hw_changed函数视编译选项CONFIG_LEDS_BRIGHTNESS_HW_CHANGED而定。在drivers/platform/x86/ideapad-laptop.c中,如下所示:

    1. #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
    2. void led_classdev_notify_brightness_hw_changed(
    3. struct led_classdev *led_cdev, unsigned int brightness);
    4. #else
    5. static inline void led_classdev_notify_brightness_hw_changed(
    6. struct led_classdev *led_cdev, enum led_brightness brightness) { }
    7. #endif

    如果没有定义CONFIG_LEDS_BRIGHTNESS_HW_CHANGED,即没有使能此配置选项,则为空函数;如果定义了CONFIG_LEDS_BRIGHTNESS_HW_CHANGED,即使能了此配置选项,则led_classdev_notify_brightness_hw_changed的实现在drivers/leds/led-class.c中,代码如下:

    1. void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev, unsigned int brightness)
    2. {
    3. if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
    4. return;
    5. led_cdev->brightness_hw_changed = brightness;
    6. sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
    7. }
    8. EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);

    可以看到,必须设置了brightness_hw_changed_kn(struct led_classdev中的成员struct kernfs_node *brightness_hw_changed_kn;)才能往下走。brightness_hw_changed_kn是在哪里赋值的?是在上一篇文章中最后提到的led_classdev_register_ext函数,其中有这样一段:

    1. if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
    2. ret = led_add_brightness_hw_changed(led_cdev);
    3. if (ret) {
    4. device_unregister(led_cdev->dev);
    5. led_cdev->dev = NULL;
    6. mutex_unlock(&led_cdev->led_access);
    7. return ret;
    8. }
    9. }

    led_add_brightness_hw_changed函数在drivers/leds/led-class.c中实现,代码如下:

    1. static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
    2. {
    3. struct device *dev = led_cdev->dev;
    4. int ret;
    5. ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
    6. if (ret) {
    7. dev_err(dev, "Error creating brightness_hw_changed\n");
    8. return ret;
    9. }
    10. led_cdev->brightness_hw_changed_kn =
    11. sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
    12. if (!led_cdev->brightness_hw_changed_kn) {
    13. dev_err(dev, "Error getting brightness_hw_changed kn\n");
    14. device_remove_file(dev, &dev_attr_brightness_hw_changed);
    15. return -ENXIO;
    16. }
    17. return 0;
    18. }

    其中,device_create_file函数和Linux内核中ideapad-laptop.c文件全解析3_蓝天居士的博客-CSDN博客中分析的device_add_groups函数都在drivers/base/core.c中实现,代码如下:

    1. /**
    2. * device_create_file - create sysfs attribute file for device.
    3. * @dev: device.
    4. * @attr: device attribute descriptor.
    5. */
    6. int device_create_file(struct device *dev,
    7. const struct device_attribute *attr)
    8. {
    9. int error = 0;
    10. if (dev) {
    11. WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
    12. "Attribute %s: write permission without 'store'\n",
    13. attr->attr.name);
    14. WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
    15. "Attribute %s: read permission without 'show'\n",
    16. attr->attr.name);
    17. error = sysfs_create_file(&dev->kobj, &attr->attr);
    18. }
    19. return error;
    20. }
    21. EXPORT_SYMBOL_GPL(device_create_file);

    device_create_file函数在/sys/devices/下创建结点。

    sysfs_get_dirent函数的作用是增加目录parent_sd中名为name的目录或文件的引用计数。其在include/linux/sysfs.h中声明和实现,代码如下:

    1. static inline struct kernfs_node *sysfs_get_dirent(struct kernfs_node *parent,
    2. const char *name)
    3. {
    4. return kernfs_find_and_get(parent, name);
    5. }

    可以看到,brightness_hw_changed_kn是在led_add_brightness_hw_changed函数中赋值的。

    回到led_classdev_notify_brightness_hw_changed函数中,看最后一句:sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);。

    sysfs_notify_dirent函数同样在include/linux/sysfs.h中声明和实现,代码如下:

    1. static inline void sysfs_notify_dirent(struct kernfs_node *kn)
    2. {
    3. kernfs_notify(kn);
    4. }

  • 相关阅读:
    终端关闭或用户退出登录,Linux命令继续运行
    Dcloud开发者注册,uniCloud服务空间创建。
    Mybatis - 查询功能
    为什么 JavaScript 中的 0.1 + 0.2 不等于 0.3
    Win11关闭Superfetch服务的操作方法分享
    基于 HBase & Phoenix 构建实时数仓(1)—— Hadoop HA 安装部署
    Unity编辑器扩展之CustomPropertyDrawer理解
    安装 Unity 个人免费版
    Spring 事务编程实践
    小红书商品详情API接口(商品详情页面数据接口)
  • 原文地址:https://blog.csdn.net/phmatthaus/article/details/128145011