linux input事件丢失/内核态文件操作

#操作系统:Linux 4.14
#驱动开发:input子系统 按键输入 内核态文件读写操作
#项目场景:使用ZYNQ处理器,运行Linux系统,通过GPIO输入读取16个按键(开关)状态。编写了input子系统框架下的驱动程序,当按键按键或者松开时上报了event事件。应用软件读取event事件代码code,进行相应的处理。
#运行顺序:Linux系统启动后,启动脚本通过modprobe指令加载驱动,然后再运行应用软件。
#存在问题:
加电后初始状态,按键有按下的也有松开的。驱动加载后读取按键状态,将按下的按键code值上报event事件,但是由于此时应用还没有加载,所以上报的事件被丢弃,应用没有获取到最初始的事件codes,应用的初始状态不对,需要手动操作一下按键,应用才能获取到正常状态。
但是如果先加载应用的话,由于没有加载驱动所以应用是找不到/dev/input/evnet0文件的。因为只有加载了驱动,才会自动生成/dev/input/evnet0设备文件。
所以只能先加载驱动,再加载应用打开文件,但是这样又会导致驱动上报的初始事件没有应用可以接收到。
#解决思路:
1、input子系统上报的evet事件是否可以保留,当应用启动之后可以读取到这些事件codes?现在感觉是上报的event事件,如果没有应用去获取,就直接被丢弃了。给驱动加载了打印信息调试,驱动加载后是可以上报这些evnet的,但是应用去读的时候已经没有了。
2、在驱动里家里文件操作,驱动加载后,将按键的初始codes写入到一个指定的文件种,然后应用启动后去读取这个指定文件,获取code初始状态。但是这样改了之后,也存在一个问题就是,驱动去写入文件的时候,有时候可以正常创建/打开文件,有时候会报文件创建/打开失败,有时候直接导致文件系统崩溃了。(是在Linux系统启动后,通过modprobe指令动态加载的驱动,这个时候文件系统应该已经正常启动了)驱动里读写文件的操作代码如下:

static int mykey_WriteValue(int code)
{
    struct file *fp = NULL;
    mm_segment_t old_fs;
    loff_t pos;
    int ret=0;
char code_data = 0;

    printk("mykey_WriteValue: code = %d\n",code);
    code_data = (char)code;

    fp = filp_open("/home/root/UserValue", O_RDWR | O_CREAT, 0666);
    if (IS_ERR(fp))
     {
         printk("mykey_WriteValue: save_data_to_file create file error\n");
         return -1;
     }

    old_fs =get_fs();
    set_fs(KERNEL_DS);
    pos =0;
    ret=kernel_write(fp, &code_data, 1, &pos);
    printk("mykey_WriteValue: kernel_write return ret=%d\n",ret);


    filp_close(fp,NULL);
    set_fs(old_fs);

    return 0;
}

#提问目的:
1、解决应用不能获取到驱动先发送的初始event事件code的问题
2、可以从保留event事件code的思路解决,也可以解决驱动读写文件不成功的问题。
3、其他有效的解决办法也许(周期上报事件的方法不行,这个试了可以,但是下一级用户不接收这个办法)

结合ChatGPT部分内容回答;
1、保留event事件code的思路是可行的,可以在驱动中使用一个缓存区来存储event事件code,当应用启动后再将缓存区中的事件code发送给应用。可以使用一个队列来实现缓存区,当驱动上报事件时将事件code加入队列,应用启动后从队列中读取事件code即可。

2、在驱动中读写文件的操作需要注意文件的权限和路径,可以尝试使用绝对路径来打开文件,例如使用"/tmp/UserValue"来代替"/home/root/UserValue"。此外,还需要确保文件系统已经正常挂载,可以在驱动中加入等待文件系统挂载完成的代码。

3、另外一种解决办法是使用udev规则来自动加载驱动和应用程序。可以在udev规则中指定驱动和应用程序的加载顺序,确保驱动先加载,然后再加载应用程序。这样可以避免驱动上报事件被丢弃的问题。

4、可以在驱动中加入一个ioctl接口,当应用程序调用该接口时,驱动将当前按键状态的event事件code发送给应用程序。这样应用程序就可以获取到初始状态的event事件code了。

5、可以在应用程序中加入一个初始化函数,在该函数中循环读取event事件,直到读取到所有按键的初始状态为止。这样虽然需要手动操作一下按键,但是可以保证应用程序获取到正确的初始状态。

既然在驱动里实现该功能,直接提供驱动接口与应用软件通讯就行了。低速信号没有必要用事件~

可以借鉴下

static char* filp_sample(const char *path, int *len)

{

mm_segment_t old;

struct file * fp = NULL;

char *save;

int size;

struct kstat stat;

loff_t pos=0;

fp = filp_open(real_path, O_RDONLY , 0);

if (IS_ERR(fp))

{

printk("open %s failed,errno:%d\n", path, (int)PTR_ERR(fp));

goto fail;

}

old = get_fs();

set_fs(KERNEL_DS);

vfs_getattr(&fp->f_path, &stat);

size = stat.size;

*len = size;

save = kzalloc(size, GFP_KERNEL);

if (!save)

{

goto fail;

}

vfs_read(fp, save, size, &pos);

filp_close(fp, NULL);

set_fs(old);

return save;

fail:

set_fs(old);

if (!IS_ERR(fp))

filp_close(fp, NULL);

return NULL;

}

驱动中IoCreateNotificationEvent,KeClearEvent 应用中OpenEvent(SYNCHRONIZE, FALSE, EVENT_NAME)
这样,只能在应用中WaitForSingleObject,而不能SetEvent,ResetEvent

可以在驱动中使用FIFO队列(Circular Queue)缓存input事件,应用程序启动后再将之前缓存的input事件发送出去,以解决应用不能获取初始化状态的问题。