zoukankan      html  css  js  c++  java
  • 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相关文件位于kerneldriversmedia c

    1.1首先来看kerneldriversmedia cMakefile

     

    如上图所示,由于我们板子上的红外接收编码是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然后在driversmedia ckeymaps里存了各种不同的键映射文件

    先来看看driversmedia ckeymapsMakefile:

     

    如上图所示,可以看到把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 "); 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
    ");
                              } else {
                                       rc_repeat(dev);               //dev->keypressed是未松开,则上报事件
                                       IR_dprintk(1, "Repeat last key
    ");
                                       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
    ");
                                       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
    ",
                                          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
    ", scancode);
                      } else if ((address ^ not_address) != 0xff) {
                              /* Extended NEC */
                              scancode = address     << 16 |
                                          not_address <<  8 |
                                          command;
                              IR_dprintk(1, "NEC (Ext) scancode 0x%06x
    ", scancode);
                      } else {
                              /* Normal NEC */
                              scancode = address << 8 | command;
                              IR_dprintk(1, "NEC scancode 0x%04x
    ", 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里存储的才是表示可重复按下的按键.

    修改之前需要将rc-map.h下的rc_map结构体里添加两个成员repeat_key和repeat_size(用来实现重复按下功能)

    修改后的结构体如下所示:

    struct rc_map {
        struct rc_map_table    *scan;
        unsigned int        size;    /* Max number of entries */
        unsigned int        len;    /* Used number of entries */
        unsigned int        alloc;    /* Size of *scan in bytes */
        enum rc_type        rc_type;
        const char        *name;
        spinlock_t        lock;
        struct rc_map_table *repeat_key;         //add
        unsigned int repeat_size;                //add
    };            

    然后修改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=0;
        static unsigned char repet_flag=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) ", 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)) {
           repet_flag = 0;
    data->state = STATE_BIT_PULSE; return 0; } else if (eq_margin(ev.duration, NEC_REPEAT_SPACE, NEC_UNIT / 2)) { //处理重复编码

          if(!repet_flag)     //避免第一次按下,出现两次编码
          {
            data->state = STATE_INACTIVE;
            repet_flag =1;
          }
          else
            data->state = STATE_TRAILER_SPACE;

               IR_dprintk(1, "Discarding last key repeat: event after key up
    ");
               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
    ");
                  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
    ",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)
    ",
              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) 在driversmedia ckeymaps里创建一个名字为rc-my-text.c键值映射文件

     

    下章链接:  46.Linux-创建rc红外遥控平台设备,实现重复功能(2)

  • 相关阅读:
    Asp.NET下生成HTML分析
    Iframe中使用JS事先切换背景颜色、背景图片的效果
    document.execCommand()函数可用参数解析
    【原创】Asp中使用Ajax实现无刷新调用页面。
    windows2003服务器防止海洋木马的安全设置【重要】
    用Asp.net屏蔽F5、Ctrl+N、Alt+F4
    用Wix制作VSPackage的安装包
    .NET应用程序调试总结系列视频(二):VS Debugger Basics
    .NET应用程序调试总结系列视频(一):总体思路
    为MyEclipse安装google app engine插件
  • 原文地址:https://www.cnblogs.com/lifexy/p/9783694.html
Copyright © 2011-2022 走看看