46.Linux-分析rc紅外遙控平臺驅動框架,修改核心的NEC解碼函式BUG(1)
本章內容
- 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 { intgpio_nr;//紅外接收管對應的管腳 boolactive_low;//資料是否低電平有效 u64allowed_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鍵值對映檔案
未完待續~