目录
4 kernel driver notify 实现poll 状态上报
4.1 brigntness 节点的写法是调用sysfs_notify()
4.2 brightness_hw_changed 是调用 sysfs_notify_dirent()实现的
1 poll 调用的实现
高通的led代码为例进行分析和学习
/* Event types that can be polled for. These bits may be set in `events' to indicate the interesting event types; they will appear in `revents' to indicate the status of the file descriptor. */ #define POLLIN 0x001 /* There is data to read. */ #define POLLPRI 0x002 /* There is urgent data to read. */ #define POLLOUT 0x004 /* Writing now will not block. */
//@files: kernel/msm-5.4/tools/leds/led_hw_brightness_mon.c
int main(int argc, char const *argv[])
{
int fd, ret;
char brightness_file_path[LED_MAX_NAME_SIZE + 11];
struct pollfd pollfd;
struct timespec ts;
char buf[11];
if (argc != 2) {
fprintf(stderr, "Requires <device-name> argument\n");
return 1;
}
snprintf(brightness_file_path, LED_MAX_NAME_SIZE,
"/sys/class/leds/%s/brightness_hw_changed", argv[1]);
fd = open(brightness_file_path, O_RDONLY); //创建要监听的fd文件
if (fd == -1) {
printf("Failed to open %s file\n", brightness_file_path);
return 1;
}
/*
* read may fail if no hw brightness change has occurred so far,
* but it is required to avoid spurious poll notifications in
* the opposite case.
*/
read(fd, buf, sizeof(buf));
pollfd.fd = fd;
pollfd.events = POLLPRI;//设定监听的event
while (1) {
ret = poll(&pollfd, 1, -1); //poll的入参含义:设定要监听的文件节点和文件节点个数,以及等待的时间
if (ret == -1) {
printf("Failed to poll %s file (%d)\n",
brightness_file_path, ret);
ret = 1;
break;
}
clock_gettime(CLOCK_MONOTONIC, &ts);
ret = read(fd, buf, sizeof(buf));
if (ret < 0)
break;
ret = lseek(pollfd.fd, 0, SEEK_SET);//监听的事件有返回则做相应的事情
if (ret < 0) {
printf("lseek failed (%d)\n", ret);
break;
}
printf("[%ld.%09ld] %d\n", ts.tv_sec, ts.tv_nsec, atoi(buf));
}
close(fd);
return ret;
}
2 poll () 调用原理:
调用到驱动注册的poll, 在驱动注册的poll 中会进行wait_poll 等event,另一条thread 可以设定event,poll 就可以检测到event就会受到POLLIN or POLLRI 的返回值,对比返回值符合预期就可以进行相关处理操作。
kernel-5.10/fs/select.c 折叠源码
//@files: kernel-5.10/tools/include/nolibc/nolibc.h
static __attribute__((unused))
int sys_poll(struct pollfd *fds, int nfds, int timeout)
{
return my_syscall3(__NR_poll, fds, nfds, timeout);
}
#define my_syscall3(num, arg1, arg2, arg3) \
({ \
long _ret; \
register long _num asm("rax") = (num); \
register long _arg1 asm("rdi") = (long)(arg1); \
register long _arg2 asm("rsi") = (long)(arg2); \
register long _arg3 asm("rdx") = (long)(arg3); \
\
asm volatile ( \
"syscall\n" \
: "=a" (_ret) \
: "r"(_arg1), "r"(_arg2), "r"(_arg3), \
"0"(_num) \
: "rcx", "r8", "r9", "r10", "r11", "memory", "cc" \
); \
_ret; \
})
//@files:kernel-5.10/fs/select.c
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
int, timeout_msecs)
{
struct timespec64 end_time, *to = NULL;
int ret;
if (timeout_msecs >= 0) {
to = &end_time;
poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
}
// 调用do_sys_poll()
ret = do_sys_poll(ufds, nfds, to);
if (ret == -ERESTARTNOHAND) {
struct restart_block *restart_block;
restart_block = ¤t->restart_block;
restart_block->poll.ufds = ufds;
restart_block->poll.nfds = nfds;
if (timeout_msecs >= 0) {
restart_block->poll.tv_sec = end_time.tv_sec;
restart_block->poll.tv_nsec = end_time.tv_nsec;
restart_block->poll.has_timeout = 1;
} else
restart_block->poll.has_timeout = 0;
ret = set_restart_fn(restart_block, do_restart_poll);
}
return ret;
}
do_sys_poll()
|-->poll_initwait(&table);//最终table->pt->qproc = __pollwait
|--> do_poll()
|--> do_pollfd(pfd, pt)
|--> vfs_poll(f.file, pwait)//@files:kernel-5.10/include/linux/poll.h
|-->return file->f_op->poll(file, pt)//调用kernel driver 注册的poll
//这个函数就跟驱动相关了,猜测调用poll_wait将当前进程放到驱动的等待列表。如果有数据的话,那么设置mask = POLLIN
|--> schedule_timeout(__timeout);//设置超时时间,进程休眠
return file->f_op->poll(file, pt)//调用kernel driver 注册的poll
//这个函数就跟驱动相关了,猜测调用poll_wait将当前进程放到驱动的等待列表。如果有数据的话,那么设置mask = POLLIN
3 kernel driver 的 .poll 实现
kernfs_fop_poll 折叠源码
const struct file_operations kernfs_file_fops = {
.read_iter = kernfs_fop_read_iter,
.write_iter = kernfs_fop_write_iter,
.llseek = generic_file_llseek,
.mmap = kernfs_fop_mmap,
.open = kernfs_fop_open,
.release = kernfs_fop_release,
.poll = kernfs_fop_poll,
.fsync = noop_fsync,
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,
};
/*
* Kernfs attribute files are pollable. The idea is that you read
* the content and then you use 'poll' or 'select' to wait for
* the content to change. When the content changes (assuming the
* manager for the kobject supports notification), poll will
* return EPOLLERR|EPOLLPRI, and select will return the fd whether
* it is waiting for read, write, or exceptions.
* Once poll/select indicates that the value has changed, you
* need to close and re-open the file, or seek to 0 and read again.
* Reminder: this only works for attributes which actively support
* it, and it is not possible to test an attribute from userspace
* to see if it supports poll (Neither 'poll' nor 'select' return
* an appropriate error code). When in doubt, set a suitable timeout value.
*/
__poll_t kernfs_generic_poll(struct kernfs_open_file *of, poll_table *wait)
{
struct kernfs_node *kn = kernfs_dentry_node(of->file->f_path.dentry);
struct kernfs_open_node *on = kn->attr.open;
//调用poll_wait将当前进程放到驱动的等待列表。如果有数据的话,那么设置mask = POLLIN
poll_wait(of->file, &on->poll, wait);
if (of->event != atomic_read(&on->event))
return DEFAULT_POLLMASK|EPOLLERR|EPOLLPRI;
return DEFAULT_POLLMASK;
}
static __poll_t kernfs_fop_poll(struct file *filp, poll_table *wait)
{
struct kernfs_open_file *of = kernfs_of(filp);
struct kernfs_node *kn = kernfs_dentry_node(filp->f_path.dentry);
__poll_t ret;
if (!kernfs_get_active(kn))
return DEFAULT_POLLMASK|EPOLLERR|EPOLLPRI;
if (kn->attr.ops->poll)
ret = kn->attr.ops->poll(of, wait);
else
ret = kernfs_generic_poll(of, wait);
kernfs_put_active(kn);
return ret;
}
4 kernel driver notify 实现poll 状态上报
本质是用一个workqueue 去等 wake_up_interruptible(&on→poll); 唤醒
4.1 brigntness 节点的写法是调用sysfs_notify()
kernel-5.10/drivers/misc/mediatek/leds/leds-mtk-disp.c
static int led_level_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
sysfs_notify(&led_cdev->dev->kobj, NULL, "brightness");
sysfs_notify 折叠源码
//@files:kernel-5.10/fs/sysfs/file.c
void sysfs_notify(struct kobject *kobj, const char *dir, const char *attr)
{
struct kernfs_node *kn = kobj->sd, *tmp;
if (kn && dir)
kn = kernfs_find_and_get(kn, dir);
else
kernfs_get(kn);
if (kn && attr) {
tmp = kernfs_find_and_get(kn, attr);
kernfs_put(kn);
kn = tmp;
}
if (kn) {
kernfs_notify(kn);
kernfs_put(kn);
}
}
EXPORT_SYMBOL_GPL(sysfs_notify);
//@files:/miui-s-matisse-dev/kernel/kernel-5.10/fs/kernfs/file.c
/**
* kernfs_notify - notify a kernfs file
* @kn: file to notify
*
* Notify @kn such that poll(2) on @kn wakes up. Maybe be called from any
* context.
*/
void kernfs_notify(struct kernfs_node *kn)
{
static DECLARE_WORK(kernfs_notify_work, kernfs_notify_workfn);
unsigned long flags;
struct kernfs_open_node *on;
if (WARN_ON(kernfs_type(kn) != KERNFS_FILE))
return;
/* kick poll immediately */
spin_lock_irqsave(&kernfs_open_node_lock, flags);
on = kn->attr.open;
if (on) {
atomic_inc(&on->event);
//唤醒poll 进程,这样poll_wait 就可以等到数据了
wake_up_interruptible(&on->poll);
}
spin_unlock_irqrestore(&kernfs_open_node_lock, flags);
/* schedule work to kick fsnotify */
spin_lock_irqsave(&kernfs_notify_lock, flags);
if (!kn->attr.notify_next) {
kernfs_get(kn);
kn->attr.notify_next = kernfs_notify_list;
kernfs_notify_list = kn;
schedule_work(&kernfs_notify_work);
}
spin_unlock_irqrestore(&kernfs_notify_lock, flags);
}
EXPORT_SYMBOL_GPL(kernfs_notify);
4.2 brightness_hw_changed 是调用 sysfs_notify_dirent()实现的
void mt_leds_notify_brightness_hw_changed(struct led_conf_info *led_conf,
enum led_brightness brightness)
{
if (WARN_ON(!led_conf->brightness_hw_changed_kn))
return;
led_conf->brightness_hw_changed = brightness;
//调用到poll 去通知
sysfs_notify_dirent(led_conf->brightness_hw_changed_kn);
}
EXPORT_SYMBOL_GPL(mt_leds_notify_brightness_hw_changed);
sysfs_notify_dirent 会调用到poll
sysfs_notify_dirent() 折叠源码
//@files:kernel-5.10/include/linux/sysfs.h
static inline void sysfs_notify_dirent(struct kernfs_node *kn)
{
kernfs_notify(kn);
}
//@files:/miui-s-matisse-dev/kernel/kernel-5.10/fs/kernfs/file.c
/**
* kernfs_notify - notify a kernfs file
* @kn: file to notify
*
* Notify @kn such that poll(2) on @kn wakes up. Maybe be called from any
* context.
*/
void kernfs_notify(struct kernfs_node *kn)
{
static DECLARE_WORK(kernfs_notify_work, kernfs_notify_workfn);
unsigned long flags;
struct kernfs_open_node *on;
if (WARN_ON(kernfs_type(kn) != KERNFS_FILE))
return;
/* kick poll immediately */
spin_lock_irqsave(&kernfs_open_node_lock, flags);
on = kn->attr.open;
if (on) {
atomic_inc(&on->event);
wake_up_interruptible(&on->poll);
}
spin_unlock_irqrestore(&kernfs_open_node_lock, flags);
/* schedule work to kick fsnotify */
spin_lock_irqsave(&kernfs_notify_lock, flags);
if (!kn->attr.notify_next) {
kernfs_get(kn);
kn->attr.notify_next = kernfs_notify_list;
kernfs_notify_list = kn;
schedule_work(&kernfs_notify_work);
}
spin_unlock_irqrestore(&kernfs_notify_lock, flags);
}
EXPORT_SYMBOL_GPL(kernfs_notify);