接前一篇文章《Linux内核中ideapad-laptop.c文件全解析8》,地址为:
Linux内核中ideapad-laptop.c文件全解析8_蓝天居士的博客-CSDN博客
上一回详细分析了ideapad_kbd_bl_init,这一回详细分析ideapad_acpi_notify。
ideapad_acpi_notify函数在同文件(linux-5.18.8/drivers/platform/x86/ideapad-laptop.c)中实现,源码如下:
- static void ideapad_acpi_notify(acpi_handle handle, u32 event, void *data)
- {
- struct ideapad_private *priv = data;
- unsigned long vpc1, vpc2, bit;
-
- if (read_ec_data(handle, VPCCMD_R_VPC1, &vpc1))
- return;
-
- if (read_ec_data(handle, VPCCMD_R_VPC2, &vpc2))
- return;
-
- vpc1 = (vpc2 << 8) | vpc1;
-
- for_each_set_bit (bit, &vpc1, 16) {
- switch (bit) {
- case 13:
- case 11:
- case 8:
- case 7:
- case 6:
- ideapad_input_report(priv, bit);
- break;
- case 10:
- /*
- * This event gets send on a Yoga 300-11IBR when the EC
- * believes that the device has changed between laptop/
- * tent/stand/tablet mode. The EC relies on getting
- * angle info from 2 accelerometers through a special
- * windows service calling a DSM on the DUAL250E ACPI-
- * device. Linux does not do this, making the laptop/
- * tent/stand/tablet mode info unreliable, so we simply
- * ignore these events.
- */
- break;
- case 9:
- ideapad_sync_rfk_state(priv);
- break;
- case 5:
- ideapad_sync_touchpad_state(priv);
- break;
- case 4:
- ideapad_backlight_notify_brightness(priv);
- break;
- case 3:
- ideapad_input_novokey(priv);
- break;
- case 2:
- ideapad_backlight_notify_power(priv);
- break;
- case 1:
- /*
- * Some IdeaPads report event 1 every ~20
- * seconds while on battery power; some
- * report this when changing to/from tablet
- * mode; some report this when the keyboard
- * backlight has changed.
- */
- ideapad_kbd_bl_notify(priv);
- break;
- case 0:
- ideapad_check_special_buttons(priv);
- break;
- default:
- dev_info(&priv->platform_device->dev,
- "Unknown event: %lu\n", bit);
- }
- }
- }
函数一上来通过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中:
- #define for_each_set_bit(bit, addr, size) \
- for ((bit) = find_next_bit((addr), (size), 0); \
- (bit) < (size); \
- (bit) = find_next_bit((addr), (size), (bit) + 1))
find_next_bit在同文件中定义并实现,代码如下:
- #ifndef find_next_bit
- /**
- * find_next_bit - find the next set bit in a memory region
- * @addr: The address to base the search on
- * @offset: The bitnumber to start searching at
- * @size: The bitmap size in bits
- *
- * Returns the bit number for the next set bit
- * If no bits are set, returns @size.
- */
- static inline
- unsigned long find_next_bit(const unsigned long *addr, unsigned long size,
- unsigned long offset)
- {
- if (small_const_nbits(size)) {
- unsigned long val;
-
- if (unlikely(offset >= size))
- return size;
-
- val = *addr & GENMASK(size - 1, offset);
- return val ? __ffs(val) : size;
- }
-
- return _find_next_bit(addr, NULL, size, offset, 0UL, 0);
- }
- #endif
下边还是分析ideapad_kbd_bl_notify函数,其在drivers/platform/x86/ideapad-laptop.c中,代码如下:
- static void ideapad_kbd_bl_notify(struct ideapad_private *priv)
- {
- int brightness;
-
- if (!priv->kbd_bl.initialized)
- return;
-
- brightness = ideapad_kbd_bl_brightness_get(priv);
- if (brightness < 0)
- return;
-
- if (brightness == priv->kbd_bl.last_brightness)
- return;
-
- priv->kbd_bl.last_brightness = brightness;
-
- led_classdev_notify_brightness_hw_changed(&priv->kbd_bl.led, brightness);
- }
如果kbd_bl.initialized为false,即没有被初始化过,则直接返回。根据《Linux内核中ideapad-laptop.c文件全解析7》中的介绍,在ideapad_kbd_bl_init函数中,kbd_bl.initialized已经被设置为了true。
ideapad_kbd_bl_brightness_get函数在同文件中实现,代码如下:
- static int ideapad_kbd_bl_brightness_get(struct ideapad_private *priv)
- {
- unsigned long hals;
- int err;
-
- err = eval_hals(priv->adev->handle, &hals);
- if (err)
- return err;
-
- return !!test_bit(HALS_KBD_BL_STATE_BIT, &hals);
- }
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中,如下所示:
- #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
- void led_classdev_notify_brightness_hw_changed(
- struct led_classdev *led_cdev, unsigned int brightness);
- #else
- static inline void led_classdev_notify_brightness_hw_changed(
- struct led_classdev *led_cdev, enum led_brightness brightness) { }
- #endif
如果没有定义CONFIG_LEDS_BRIGHTNESS_HW_CHANGED,即没有使能此配置选项,则为空函数;如果定义了CONFIG_LEDS_BRIGHTNESS_HW_CHANGED,即使能了此配置选项,则led_classdev_notify_brightness_hw_changed的实现在drivers/leds/led-class.c中,代码如下:
- void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev, unsigned int brightness)
- {
- if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
- return;
-
- led_cdev->brightness_hw_changed = brightness;
- sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
- }
- 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函数,其中有这样一段:
- if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
- ret = led_add_brightness_hw_changed(led_cdev);
- if (ret) {
- device_unregister(led_cdev->dev);
- led_cdev->dev = NULL;
- mutex_unlock(&led_cdev->led_access);
- return ret;
- }
- }
led_add_brightness_hw_changed函数在drivers/leds/led-class.c中实现,代码如下:
- static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
- {
- struct device *dev = led_cdev->dev;
- int ret;
-
- ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
- if (ret) {
- dev_err(dev, "Error creating brightness_hw_changed\n");
- return ret;
- }
-
- led_cdev->brightness_hw_changed_kn =
- sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
- if (!led_cdev->brightness_hw_changed_kn) {
- dev_err(dev, "Error getting brightness_hw_changed kn\n");
- device_remove_file(dev, &dev_attr_brightness_hw_changed);
- return -ENXIO;
- }
-
- return 0;
- }
其中,device_create_file函数和Linux内核中ideapad-laptop.c文件全解析3_蓝天居士的博客-CSDN博客中分析的device_add_groups函数都在drivers/base/core.c中实现,代码如下:
- /**
- * device_create_file - create sysfs attribute file for device.
- * @dev: device.
- * @attr: device attribute descriptor.
- */
- int device_create_file(struct device *dev,
- const struct device_attribute *attr)
- {
- int error = 0;
-
- if (dev) {
- WARN(((attr->attr.mode & S_IWUGO) && !attr->store),
- "Attribute %s: write permission without 'store'\n",
- attr->attr.name);
- WARN(((attr->attr.mode & S_IRUGO) && !attr->show),
- "Attribute %s: read permission without 'show'\n",
- attr->attr.name);
- error = sysfs_create_file(&dev->kobj, &attr->attr);
- }
-
- return error;
- }
- EXPORT_SYMBOL_GPL(device_create_file);
device_create_file函数在/sys/devices/下创建结点。
sysfs_get_dirent函数的作用是增加目录parent_sd中名为name的目录或文件的引用计数。其在include/linux/sysfs.h中声明和实现,代码如下:
- static inline struct kernfs_node *sysfs_get_dirent(struct kernfs_node *parent,
- const char *name)
- {
- return kernfs_find_and_get(parent, name);
- }
可以看到,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中声明和实现,代码如下:
- static inline void sysfs_notify_dirent(struct kernfs_node *kn)
- {
- kernfs_notify(kn);
- }