嵌入式Linux——应用调试:输入模拟器(复现操作)

2019-07-12 15:16发布

简介:     本文主要介绍一种可以复现操作的方法。通过该方法我们可以复现我们前面做过的操作。我们主要的思路是先将我们先前的操作记录下来,然后再将我们记录的值上报,以实现操作的复现。    Linux内核:linux-2.6.22.6  所用开发板:JZ2440 V3(S3C2440A) 声明:     本文是看完韦东山老师的视频后所写的课程总结,同时文中也会加入一些我自己对这方面知识的理解。希望可以帮到你们。 输入模拟器想法:     1. 产品要经过测试才能发布,一般都是人工操作,比如手机触摸屏,遥控器。     2. 操作过程中发现错误,要再次复现找到规律从而修改程序。     3. 能否在驱动程序中将所有的操作记录下来,存为文件。当出现错误时,可以通过文件里的数据来复现输入过程。 程序写作思路:     使用输入子系统中的触摸屏程序来进行说明。因为触摸屏程序中有input_event函数。我们在使用input_event函数上报事件的同时使用我们前面写的myprintk函数来记录上报的内容。当记录完成后我们可以通过cat /proc/mymsg命令来查看我们所记录的内容。当需要复现的时候我们再通过input_event函数将记录的数据输出,做到操作的复现。     而关于触摸屏的程序可以参考:嵌入式Linux ———触摸屏驱动开发     而关于myprintk函数可以参考:嵌入式Linux——kmsg:分析/proc/kmsg文件以及写自己的/proc/mymsg 记录上报参数:     我们看input_event函数,来了解我们都要上报什么信息: /** * input_event() - 上报新的输入事件 * @dev: 产生事件的设备 * @type: 事件类型 * @code: 具体事件码 * @value: 事件值 */ void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) {         ······ switch (type) { case EV_SYN: switch (code) { case SYN_CONFIG: case SYN_REPORT: } break; case EV_KEY: break; case EV_SW: break; case EV_ABS: break; case EV_REL: break; case EV_MSC: break; case EV_LED: break; case EV_SND: break; case EV_REP: break; case EV_FF: break; } }     从上面看input_event函数的参数有:dev,type,code,val。因为我们知道要记录的主要是事件的值,所以发生事件的设备就不记录了,取而代之的是我们要记录上报这个事件的时间time。这里我们使用jiffies来记录时间。所以我们要记录的内容为time,type,code,val。     同时由于我们要使用myprintk函数来将上报内容记录到mymsg中,所以在这个程序里我们要声明myprintk函数: extern int myprintk(const char *fmt,...);     下面我们就要写一个记录函数来将这些重要的信息记录下来了。 void report_to_printk(unsigned int time,unsigned int type,unsigned int code,int val) { myprintk("0x%08x 0x%08x 0x%08x %d ",time,type,code,val); } 而记录信息的格式为:    0x00076617 0x00000003 0x00000018 0 0x00076617 0x00000003 0x00000018 0 0x0007661b 0x00000003 0x00000018 0 0x0007661b 0x00000003 0x00000018 0     接下来我们就要到触摸屏程序中找上报事件了,我们找到上报事件后在其后加入report_to_printk函数,将上报的内容记录下来。例如: input_report_abs(s3c_ts_dev,ABS_PRESSURE,0); report_to_printk(jiffies,EV_ABS,ABS_PRESSURE,0); input_report_key(s3c_ts_dev,BTN_TOUCH,0); report_to_printk(jiffies,EV_KEY,BTN_TOUCH,0); input_sync(s3c_ts_dev); report_to_printk(jiffies,EV_SYN,SYN_REPORT,0); input_report_abs(s3c_ts_dev,ABS_X,(x[0]+x[1]+x[2]+x[3])/4); report_to_printk(jiffies,EV_ABS,ABS_X,(x[0]+x[1]+x[2]+x[3])/4); input_report_abs(s3c_ts_dev,ABS_Y,(y[0]+y[1]+y[2]+y[3])/4); report_to_printk(jiffies,EV_ABS,ABS_Y,(y[0]+y[1]+y[2]+y[3])/4); input_report_abs(s3c_ts_dev,ABS_PRESSURE,1); report_to_printk(jiffies,EV_ABS,ABS_PRESSURE,1); input_report_key(s3c_ts_dev,BTN_TOUCH,1); report_to_printk(jiffies,EV_KEY,BTN_TOUCH,1); input_sync(s3c_ts_dev); report_to_printk(jiffies,EV_SYN,SYN_REPORT,0);     修改完代码我们就可以装载这些驱动了。但是这里提醒一下,在装载驱动时,要先装载记录事件mymsg.ko的驱动,再装载触摸屏s3c_ts.ko的驱动。而如果先装载触摸屏的驱动就会出现:Unknown symbol myprintk 的错误提示而且不能装载这个驱动,这是因为我们在触摸屏中用到了myprintk函数,但是我们并没有定义它,所以我们要先装载记录事件mymsg.ko的驱动,再装载触摸屏s3c_ts.ko的驱动。     装载完两个驱动我们就可以调试通过cat /proc/mymsg命令查看记录信息,同时我们可以通过命令: cp /proc/mymsg  /ts.txt 将记录的数据保存到一个文件中。 复现记录:     现在我们要完成另一个功能:从文件中得到记录的数据,并上报这些数据以实现复现功能。因此我们要在触摸屏程序中再添加一个字符驱动程序来完成上面的功能。这个字符驱动程序中有: write函数:将文件中的数据写入驱动程序的buf中。 ioctl函数:启动复现功能,同时加入标记功能来记录重要信息的位置。     我先将驱动框架写出: int auto_major = 0; static struct class *cls; static ssize_t replay_write (struct file *file, const char __user *buf, size_t size, loff_t *loff_t) { return 0; } static int replay_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { return 0; } static struct file_operations replay_fops = { .owner = THIS_MODULE, .write = replay_write, .ioctl = replay_ioctl, }; 入口函数: auto_major = register_chrdev(0,"replay",&replay_fops); cls = class_create(THIS_MODULE,"replay"); device_create(cls,NULL,MKDEV(auto_major,0),"replay"); 出口函数: device_destroy(cls,MKDEV(auto_major,0)); class_destroy(cls); unregister_chrdev(auto_major,"replay");     而要完成其他函数之前,我们要先申请一块空间来存放写入驱动的数据,所以我们先申请空间: static char *replay_buf; 入口函数: replay_buf = kmalloc(1024*1024,GFP_KERNEL); if(!replay_buf){ printk("can't alloc for mylog_buf "); return -EIO; } 出口函数: kfree(replay_buf);     写函数:将文件中的数据写入驱动程序的buf中。 static int replay_w = 0; /* 记录数据写入的位置 */ static int replay_r = 0; /* 记录数据读取的位置 */ static ssize_t replay_write (struct file *file, const char __user *buf, size_t size, loff_t *loff_t) { int err; /* 检验是否超出replay_buf的范围,如果超出则提示错误并返回 */ if(replay_w + size > (1024*1024)){ printk(" replay_buf full! "); return -EIO; } /* 将文件从用户空间读入,这里使用replay_w记录数据存放的位置 */ err = copy_from_user(replay_buf+replay_w,buf,size); /* 这里涉及copy_from_user的返回值,当完成操作时返回0,没有完成返回非0 */ if(err){ return -EIO; }else{ replay_w += size; } return size; }     ioctl函数:启动复现功能,同时加入标记功能来记录重要信息的位置。 static int replay_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) { char buf[100]; switch (cmd){ case REPLAY_MODE_ON: { replay_timer.expires = jiffies + 1; /* 这里的超时时间为当前时间加5ms,所以我们一调用add_timer函数就可以进入处理函数了 */ del_timer(&replay_timer); /* 为了可以重复复现,在添加定时器之前要先删除定时器,防止发生错误 */ add_timer(&replay_timer); break; } case REPLAY_MODE_TAG:{ copy_from_user(buf,(const void __user *)arg,100); /* 从用户空间获得标志位 */ buf[99] = '