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鍵值映射文件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章