46.Linux-分析rc红外遥控平台驱动框架,修改内核的NEC解码函数BUG(1)
- 内核版本 : linux 3.10.14
- rc红外接收类型: gpio 类型的nec红外编码
本章内容
- 1) rc体系结构分析
- 2) 分析红外platform_driver平台驱动框架
- 3) 分析内核自带的nec红外解码过程
- 4) 修改内核自带的nec红外解码bug,实现按键重复按下
下章内容
- 1) 自己创建一个红外platform_device平台设备
- 2) 试验
在分析之前,先来复习下nec红外编码的发送波形(在后面分析nec解码会用到)
基本数据格式如下:
如果一直按住一个按钮时,会每隔100ms一直发送引导重复码.
一个完整的数据波形如下所示:
1.rc体系结构分析
rc相关文件位于kernel\drivers\media\rc
1.1首先来看kernel\drivers\media\rc\makefile
如上图所示,由于我们板子上的红外接收编码是nec格式,并且是gpio类型
所以make menuconfig配置宏:
->device drivers -> multimedia support (media_support [=y]) -> remote controller decoders (rc_decoders [=y]) [*] enable ir raw decoder for the nec protocol //选择nec协议, ,使config_ir_nec_decoder=y ->device drivers -> multimedia support (media_support [=y]) -> remote controller devices (rc_devices [=y]) [*] gpio ir remote control //选择gpio接收类型,使config_ir_gpio_cir=y
1.2然后在drivers\media\rc\keymaps里存了各种不同的键映射文件
先来看看drivers\media\rc\keymaps\makefile:
如上图所示,可以看到把keymaps文件夹里的文件全部包含了.
它们用途在于:
1) 当内核解码后,通过我们红外平台设备的dev.platform_data里map_name成员去匹配这些文件.
其中红外平台设备platform_data对应的结构体为:
struct gpio_ir_recv_platform_data { int gpio_nr; //红外接收管对应的管脚 bool active_low; //数据是否低电平有效 u64 allowed_protos; //该红外允许接收的编码协议,比如有nec, sanyo, rc5等,可以填0,表示支持所有 const char *map_name; //该红外接收管对应的键值映射表名,内核会通过该名字去匹配keymaps文件夹里的编码对应的文件.从而注册该文件的键值映射表,以后解出来的编码则去找该键值映射表 };
2) 找到对应的文件,然后便通过该文件里的rc_map_list匹配编码
我们以rc-trekstor.c文件为例,该文件内容如下所示:
3)如果匹配到支持接收的编码,便会上报input事件按键.
ps: 在下章创建红外平台设备时,会详细讲解如何使用
2.分析红外platform_driver平台驱动框架
我们选择的是config_ir_gpio_cir宏,所以接下来分析gpio类型的rc驱动框架,该宏对应的驱动文件为:
2.1 分析gpio-ir-recv.c的init入口函数
如上图所示,其中module_platform_driver()宏定义位于platform_device.h
最终module_platform_driver(gpio_ir_recv_driver)展开后等于:
static int __init gpio_ir_recv_driver_init(void) { return platform_driver_register(&gpio_ir_recv_driver); } module_init(gpio_ir_recv_driver_init); //…
该平台驱动的.name定义如下所示:
#define gpio_ir_driver_name "gpio-rc-recv"
所以我们后面创建红外platform_device平台设备时, .name也要写成"gpio-rc-recv"
2.2 分析gpio-ir-recv.c的probe函数
ps:在probe函数里,主要是获取平台设备pdev->dev.platform_data内容.该内容在1.2小结讲解过了.
代码如下:
static int gpio_ir_recv_probe(struct platform_device *pdev) { struct gpio_rc_dev *gpio_dev; struct rc_dev *rcdev; const struct gpio_ir_recv_platform_data *pdata =pdev->dev.platform_data;
//获取gpio_ir_recv_platform_data结构体 int rc; //… … if (pdata->gpio_nr < 0) //判断管脚有效性 return -einval; gpio_dev = kzalloc(sizeof(struct gpio_rc_dev), gfp_kernel); if (!gpio_dev) return -enomem; rcdev = rc_allocate_device(); if (!rcdev) { rc = -enomem; goto err_allocate_device; } rcdev->priv = gpio_dev; rcdev->driver_type = rc_driver_ir_raw; rcdev->input_name = gpio_ir_device_name; rcdev->input_phys = gpio_ir_device_name "/input0"; rcdev->input_id.bustype = bus_host; rcdev->input_id.vendor = 0x0001; rcdev->input_id.product = 0x0001; rcdev->input_id.version = 0x0100; rcdev->dev.parent = &pdev->dev; rcdev->driver_name = gpio_ir_driver_name; if (pdata->allowed_protos) rcdev->allowed_protos = pdata->allowed_protos; else rcdev->allowed_protos = rc_bit_all; // allowed_protos==0,表示支持所有协议类型 rcdev->map_name = pdata->map_name ?: rc_map_empty; gpio_dev->rcdev = rcdev; gpio_dev->gpio_nr = pdata->gpio_nr; gpio_dev->active_low = pdata->active_low; rc = gpio_request(pdata->gpio_nr, "gpio-ir-recv"); //申请io管脚 if (rc < 0) goto err_gpio_request; rc = gpio_direction_input(pdata->gpio_nr); //设置为输入 if (rc < 0) goto err_gpio_direction_input; rc = rc_register_device(rcdev); if (rc < 0) { dev_err(&pdev->dev, "failed to register rc device\n"); goto err_register_rc_device; } platform_set_drvdata(pdev, gpio_dev); rc = request_any_context_irq(gpio_to_irq(pdata->gpio_nr), gpio_ir_recv_irq, irqf_trigger_falling | irqf_trigger_rising, "gpio-ir-recv-irq", gpio_dev); //创建gpio_ir_recv_irq中断函数,为上下沿触发 return 0; //… … }
接下来,我们来看看gpio_ir_recv_irq()函数,看看如何实现解码的
2.3 分析gpio-ir-recv.c的gpio_ir_recv_irq函数
static irqreturn_t gpio_ir_recv_irq(int irq, void *dev_id) { struct gpio_rc_dev *gpio_dev = dev_id; int gval; int rc = 0; enum raw_event_type type = ir_space; //默认定义类型为ir_space (红外接收的间隔信号) gval = gpio_get_value_cansleep(gpio_dev->gpio_nr); //获取gpio的值 if (gval < 0) goto err_get_value; if (gpio_dev->active_low) //低电平有效 gval = !gval; //取反 if (gval == 1) type = ir_pulse; //收到的是脉冲信号 rc = ir_raw_event_store_edge(gpio_dev->rcdev, type); //通过内核时间,计算出当前波形的持续时间,并保存 if (rc < 0) goto err_get_value; ir_raw_event_handle(gpio_dev->rcdev); //启动内核解码对应的线程,来处理波形 err_get_value: return irq_handled; }
接下来分析ir_raw_event_handle()函数如何处理波形的.
2.4 gpio_ir_recv_irq ()->ir_raw_event_handle()函数
该函数如下所示:
如上图所示,最终会唤醒一个线程,该线程对应的函数为ir_raw_event_thread():
static int ir_raw_event_thread(void *data) { struct ir_raw_handler *handler; … … list_for_each_entry(handler, &ir_raw_handler_list, list)
// ir_raw_handler_list: 存储内核里注册的各个解码协议ir_raw_handler结构体,比如nec, sanyo, rc5等 handler->decode(raw->dev, ev); //调用解码函数 … … };
2.5 接下来,我们看看解码文件是如何添加到ir_raw_handler_list表的
由于我们选择的是nec协议(config_ir_nec_decoder=y),所以以/drivers/media/rc/ir-nec-decoder.c为例
1)首先查看ir-nec-decoder.c的init函数:
如上图所示,可以看到通过ir_raw_handler_register()来注册.
2) 然后ir_raw_handler_register()里,则将该nec_handler添加到ir_raw_handler_list表:
3.接下来,我们来分析ir_nec_decode()解码函数如何解码的.
3.1分析ir_nec_decode()解码函数
static int ir_nec_decode(struct rc_dev *dev, struct ir_raw_event ev) { struct nec_dec *data = &dev->raw->nec; u32 scancode; u8 address, not_address, command, not_command; bool send_32bits = false; if (!(dev->enabled_protocols & rc_bit_nec)) //判断协议是否支持 return 0; //… … switch (data->state) { case state_inactive: if (!ev.pulse) break; if (eq_margin(ev.duration, nec_header_pulse, nec_unit * 2)) { //判断ev.duration 是否等于9ms头引导码 data->is_nec_x = false; //标记当前格式不是necx编码格式 data->necx_repeat = false; } else if (eq_margin(ev.duration, necx_header_pulse, nec_unit / 2)) //另一种不常见的necx引导码 data->is_nec_x = true; //标记是necx编码格式 else break; data->count = 0; data->state = state_header_space; //进入判断引导码间隔值,是4.5ms还是2.25ms ? return 0; case state_header_space: if (ev.pulse) break; if (eq_margin(ev.duration, nec_header_space, nec_unit)) { //如果ev.duration=4.5ms 间隔引导码 data->state = state_bit_pulse; //进入解析32bit模式 return 0; } else if (eq_margin(ev.duration, nec_repeat_space, nec_unit / 2)) { //如果ev.duration=2.5ms ,表示重复引导码 if (!dev->keypressed) { //dev->keypressed是松开的,则放弃(这里有bug,后面会分析到) ir_dprintk(1, "discarding last key repeat: event after key up\n"); } else { rc_repeat(dev); //dev->keypressed是未松开,则上报事件 ir_dprintk(1, "repeat last key\n"); data->state = state_trailer_pulse; } return 0; } break; case state_bit_pulse: //接收数据位的脉冲数据 if (!ev.pulse) break; if (!eq_margin(ev.duration, nec_bit_pulse, nec_unit / 2)) //不等于0.56ms,则忽略掉 break; data->state = state_bit_space; //等于0.56ms,接下来进入state_bit_space,开始解析数据bit return 0; case state_bit_space: if (ev.pulse) break; if (data->necx_repeat && data->count == necx_repeat_bits && geq_margin(ev.duration, nec_trailer_space, nec_unit / 2)) { //解析necx编码格式 ir_dprintk(1, "repeat last key\n"); rc_repeat(dev); data->state = state_inactive; return 0; } else if (data->count > necx_repeat_bits) data->necx_repeat = false; data->bits <<= 1; if (eq_margin(ev.duration, nec_bit_1_space, nec_unit / 2)) // 1.68ms 数据1 data->bits |= 1; else if (!eq_margin(ev.duration, nec_bit_0_space, nec_unit / 2)) // 既不等于1.68ms,也不等于0.56ms,则是无效数据 break; data->count++; if (data->count == nec_nbits) //data->count == 32,则表示数据接收完成 data->state = state_trailer_pulse; else data->state = state_bit_pulse; return 0; case state_trailer_pulse: if (!ev.pulse) break; if (!eq_margin(ev.duration, nec_trailer_pulse, nec_unit / 2)) break; data->state = state_trailer_space; return 0; case state_trailer_space: if (ev.pulse) break; if (!geq_margin(ev.duration, nec_trailer_space, nec_unit / 2)) break; address = bitrev8((data->bits >> 24) & 0xff); not_address = bitrev8((data->bits >> 16) & 0xff); command = bitrev8((data->bits >> 8) & 0xff); not_command = bitrev8((data->bits >> 0) & 0xff); if ((command ^ not_command) != 0xff) { //解析数据 ir_dprintk(1, "nec checksum error: received 0x%08x\n", data->bits); send_32bits = true; } if (send_32bits) { /* nec transport, but modified protocol, used by at * least apple and tivo remotes */ scancode = data->bits; ir_dprintk(1, "nec (modified) scancode 0x%08x\n", scancode); } else if ((address ^ not_address) != 0xff) { /* extended nec */ scancode = address << 16 | not_address << 8 | command; ir_dprintk(1, "nec (ext) scancode 0x%06x\n", scancode); } else { /* normal nec */ scancode = address << 8 | command; ir_dprintk(1, "nec scancode 0x%04x\n", scancode); } if (data->is_nec_x) data->necx_repeat = true; rc_keydown(dev, scancode, 0); //通过scancode编码来上报按键事件 data->state = state_inactive; return 0; } //… … }
3.2接下来分析ir_nec_decode ()->rc_keydown()如何通过scancode编码来上报按键事件
void rc_keydown(struct rc_dev *dev, int scancode, u8 toggle) { unsigned long flags; u32 keycode = rc_g_keycode_from_table(dev, scancode); //从键映射表里找到编码对应的键值 spin_lock_irqsave(&dev->keylock, flags); if(keycode){ //如果找到键值 ir_do_keydown(dev, scancode, keycode, toggle); //上报按键事件 if (dev->keypressed) { //如果是按下,则启动timer_keyup定时器, ir_keypress_timeout(20ms)后上报key松开事件 dev->keyup_jiffies = jiffies + msecs_to_jiffies(ir_keypress_timeout); mod_timer(&dev->timer_keyup, dev->keyup_jiffies); } }else{ dev->last_scancode = 0; dev->last_toggle = 0; dev->last_keycode = 0; } spin_unlock_irqrestore(&dev->keylock, flags); }
上个函数里的dev->timer_keyup定时器对应的函数为ir_timer_keyup(),该函数会去调用一次ir_do_keyup()函数,上报key松开事件,该函数如下:
如上图所示,我们发现dev->keypressed = false,这就是解码函数出现的bug:
1)比如当遥控器当按下按键时,会上报一次按键按下事件,并启动20ms定时器,用来自动上报按键自动按起事件,并标记dev->keypressed = false.
2)然后,如果遥控器一直按下不松手的话,会隔110ms发送一次9ms+2.25ms重复引导码
3) 然后内核将会调用ir_nec_decode()进行解码2.25ms
4. 修改ir_nec_decode()函数
接下来,我们修改ir_nec_decode()函数,实现按键重复按下,并实现rc_map->repeat_key.
为什么要实现rc_map->repeat_key?
因为rc_map->scan里存储的键值表仅仅表示可支持按下的按键, 而rc_map->repeat_key里存储的才是表示可重复按下的按键.
修改后的代码如下所示:
static int ir_nec_decode(struct rc_dev *dev, struct ir_raw_event ev) { struct nec_dec *data = &dev->raw->nec; u32 scancode=0; u8 address, not_address, command, not_command; bool send_32bits = false; static int es9038_c28=0,es9038_c29=0,es9038_c30=0,es9038_c31=0; if (!(dev->enabled_protocols & rc_bit_nec)) return 0; if (!is_timing_event(ev)) { if (ev.reset) data->state = state_inactive; return 0; } ir_dprintk(2, "nec decode started at state %d (%uus %s)\n", data->state, to_us(ev.duration), to_str(ev.pulse)); switch (data->state) { case state_inactive: if (!ev.pulse) break; if (eq_margin(ev.duration, nec_header_pulse, nec_unit * 2)) { data->is_nec_x = false; data->necx_repeat = false; } else if (eq_margin(ev.duration, necx_header_pulse, nec_unit / 2)) { data->is_nec_x = true; } else break; data->count = 0; data->state = state_header_space; return 0; case state_header_space: if (ev.pulse) break; if (eq_margin(ev.duration, nec_header_space, nec_unit)) { data->state = state_bit_pulse; return 0; } else if (eq_margin(ev.duration, nec_repeat_space, nec_unit / 2)) { //处理重复编码 data->state = state_trailer_space; ir_dprintk(1, "discarding last key repeat: event after key up\n"); return 0; } else break; case state_bit_pulse: if (!ev.pulse) break; if (!eq_margin(ev.duration, nec_bit_pulse, nec_unit / 2)) break; data->state = state_bit_space; return 0; case state_bit_space: if (ev.pulse) break; if (data->necx_repeat && data->count == necx_repeat_bits && geq_margin(ev.duration, nec_trailer_space, nec_unit / 2)) { ir_dprintk(1, "repeat last key\n"); rc_repeat(dev); data->state = state_inactive; return 0; } else if (data->count > necx_repeat_bits) data->necx_repeat = false; data->bits <<= 1; if (eq_margin(ev.duration, nec_bit_1_space, nec_unit / 2)) data->bits |= 1; else if (!eq_margin(ev.duration, nec_bit_0_space, nec_unit / 2)) break; data->count++; if (data->count == nec_nbits) data->state = state_trailer_space; else data->state = state_bit_pulse; return 0; case state_trailer_space: { struct rc_map *rc_map = &dev->rc_map; struct rc_map_table *repeat_key = rc_map->repeat_key; unsigned int repeat_size = rc_map->repeat_size; //获取 repeat_size,是否有支持重复按下的按键 scancode=data->bits; if (!ev.pulse) break; if (!eq_margin(ev.duration, nec_trailer_pulse, nec_unit / 2)) break; printk("nec scancode=0x%x\n",scancode); if(!scancode) break; if (data->is_nec_x) data->necx_repeat = true; rc_keydown(dev, scancode, 0); //上报事件 if(repeat_key){ int i = 0; while(repeat_size){ if(scancode == repeat_key[i].scancode){ break; } repeat_size--; i++; } if(repeat_size==0) //repeat_size==0,表示没找到有支持重复按键,则清空data->bits data->bits = 0; } else data->bits = 0; return 0; } } ir_dprintk(1, "nec decode failed at count %d state %d (%uus %s)\n", data->count, data->state, to_us(ev.duration), to_str(ev.pulse)); data->state = state_inactive; return -einval; }
接下来下章,自己创建一个红外platform_device平台设备
创建红外platform_device平台设备步骤为:
- 1) 创建一个platform_device设备,其中.name= "gpio-rc-recv",并注册设备
- 2) 在drivers\media\rc\keymaps\里创建一个名字为rc-my-text.c键值映射文件
下章链接: 46.linux-创建rc红外遥控平台设备,实现重复功能(2)