網(wǎng)站備案域名更改廣點(diǎn)通
往期內(nèi)容
本專欄往期內(nèi)容:
- input子系統(tǒng)的框架和重要數(shù)據(jù)結(jié)構(gòu)詳解-CSDN博客
- input device和input handler的注冊(cè)以及匹配過(guò)程解析-CSDN博客
- input device和input handler的注冊(cè)以及匹配過(guò)程解析-CSDN博客
I2C子系統(tǒng)專欄:
- 專欄地址:IIC子系統(tǒng)_憧憬一下的博客-CSDN博客
- 具體芯片的IIC控制器驅(qū)動(dòng)程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期內(nèi)容觀看順序總線和設(shè)備樹專欄:
- 專欄地址:總線和設(shè)備樹_憧憬一下的博客-CSDN博客
- 設(shè)備樹與 Linux 內(nèi)核設(shè)備驅(qū)動(dòng)模型的整合-CSDN博客
– 末篇,有往期內(nèi)容觀看順序
前言
和之前的驅(qū)動(dòng)程序有點(diǎn)差別(IIC專欄中編寫的控制器驅(qū)動(dòng)框架編寫一個(gè)通用的i2c控制器驅(qū)動(dòng)框架-CSDN博客),在driver中變成注冊(cè)input_dev,file_operations字符驅(qū)程序的創(chuàng)建在input_handler層實(shí)現(xiàn)(原本是在platform_driver中實(shí)現(xiàn)的:file_operation、設(shè)備類的注冊(cè)),實(shí)現(xiàn)了內(nèi)核驅(qū)動(dòng)程序的上層、中轉(zhuǎn)層、下層的分離
下層驅(qū)動(dòng)中,只需要去編寫好設(shè)備的驅(qū)動(dòng)程序,在程序中分配、設(shè)置、注冊(cè)input_dev,發(fā)生中斷時(shí)只需要上報(bào)中斷事件即可,其余的中轉(zhuǎn)層和上層的驅(qū)動(dòng)程序內(nèi)核已經(jīng)做好了。
這個(gè)在之前對(duì)內(nèi)核提供的源碼示例進(jìn)行講解的時(shí)候也很清晰了,詳見(jiàn)本專欄前3章內(nèi)容。
1. 怎么編寫input_dev驅(qū)動(dòng)
這里參考內(nèi)核提供的gpio_keys.c為例子,input_dev上層
\Linux-4.9.88\drivers\input\keyboard\gpio_keys.c:📎gpio_keys.c
1.1 分配、設(shè)置、注冊(cè)input_dev
這一部分主要是probe完成:
在gpio_keys.c中,添加了自己理解的一點(diǎn)注釋,如下:
static int gpio_keys_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev; // 獲取設(shè)備結(jié)構(gòu)體const struct gpio_keys_platform_data *pdata = dev_get_platdata(dev); // 從平臺(tái)設(shè)備獲取平臺(tái)數(shù)據(jù)struct gpio_keys_drvdata *ddata; // 驅(qū)動(dòng)私有數(shù)據(jù)結(jié)構(gòu)struct input_dev *input; // 輸入設(shè)備結(jié)構(gòu)體size_t size; // 計(jì)算需要分配的內(nèi)存大小int i, error; // 循環(huán)計(jì)數(shù)器和錯(cuò)誤碼int wakeup = 0; // 標(biāo)志位,指示是否支持喚醒功能// 如果 pdata 為 NULL,則從設(shè)備樹獲取平臺(tái)數(shù)據(jù)if (!pdata) {pdata = gpio_keys_get_devtree_pdata(dev);if (IS_ERR(pdata))return PTR_ERR(pdata); // 返回錯(cuò)誤碼}// 計(jì)算 gpio_keys_drvdata 和按鈕數(shù)據(jù)結(jié)構(gòu)的大小size = sizeof(struct gpio_keys_drvdata) +pdata->nbuttons * sizeof(struct gpio_button_data);// 分配驅(qū)動(dòng)私有數(shù)據(jù)內(nèi)存ddata = devm_kzalloc(dev, size, GFP_KERNEL);if (!ddata) {dev_err(dev, "failed to allocate state\n"); // 分配失敗,輸出錯(cuò)誤信息return -ENOMEM; // 返回內(nèi)存不足錯(cuò)誤碼}// 分配輸入設(shè)備input = devm_input_allocate_device(dev);if (!input) {dev_err(dev, "failed to allocate input device\n"); // 分配失敗,輸出錯(cuò)誤信息return -ENOMEM; // 返回內(nèi)存不足錯(cuò)誤碼}ddata->pdata = pdata; // 保存平臺(tái)數(shù)據(jù)指針ddata->input = input; // 保存輸入設(shè)備指針mutex_init(&ddata->disable_lock); // 初始化互斥鎖// 將驅(qū)動(dòng)數(shù)據(jù)指針與平臺(tái)設(shè)備相關(guān)聯(lián)platform_set_drvdata(pdev, ddata);input_set_drvdata(input, ddata); // 將驅(qū)動(dòng)數(shù)據(jù)與輸入設(shè)備關(guān)聯(lián)// 設(shè)置輸入設(shè)備名稱和物理路徑input->name = pdata->name ? : pdev->name; // 如果 pdata 中有名稱則使用,否則使用平臺(tái)設(shè)備名稱input->phys = "gpio-keys/input0"; // 設(shè)置物理路徑input->dev.parent = &pdev->dev; // 設(shè)置設(shè)備的父設(shè)備input->open = gpio_keys_open; // 設(shè)置打開(kāi)設(shè)備的函數(shù)input->close = gpio_keys_close; // 設(shè)置關(guān)閉設(shè)備的函數(shù)// 設(shè)置輸入設(shè)備的 IDinput->id.bustype = BUS_HOST; // 設(shè)置總線類型input->id.vendor = 0x0001; // 設(shè)置廠商 IDinput->id.product = 0x0001; // 設(shè)置產(chǎn)品 IDinput->id.version = 0x0100; // 設(shè)置版本號(hào)// 啟用 Linux 輸入子系統(tǒng)的自動(dòng)重復(fù)功能if (pdata->rep)__set_bit(EV_REP, input->evbit); // 設(shè)置 EV_REP 事件位// 遍歷每個(gè)按鈕并設(shè)置for (i = 0; i < pdata->nbuttons; i++) {const struct gpio_keys_button *button = &pdata->buttons[i]; // 獲取當(dāng)前按鈕信息struct gpio_button_data *bdata = &ddata->data[i]; // 獲取按鈕數(shù)據(jù)error = gpio_keys_setup_key(pdev, input, bdata, button); // 設(shè)置按鍵,里面包括設(shè)置了中斷函數(shù)if (error)return error; // 返回錯(cuò)誤碼if (button->wakeup) // 如果按鈕支持喚醒功能wakeup = 1; // 設(shè)置喚醒標(biāo)志}// 創(chuàng)建 sysfs 組,用于導(dǎo)出按鍵和開(kāi)關(guān)error = sysfs_create_group(&pdev->dev.kobj, &gpio_keys_attr_group);if (error) {dev_err(dev, "Unable to export keys/switches, error: %d\n", error); // 輸出錯(cuò)誤信息return error; // 返回錯(cuò)誤碼}// 注冊(cè)輸入設(shè)備error = input_register_device(input);if (error) {dev_err(dev, "Unable to register input device, error: %d\n", error); // 輸出錯(cuò)誤信息goto err_remove_group; // 錯(cuò)誤處理,移除 sysfs 組}// 初始化喚醒設(shè)備功能device_init_wakeup(&pdev->dev, wakeup);return 0; // 返回成功err_remove_group:// 在錯(cuò)誤情況下移除 sysfs 組sysfs_remove_group(&pdev->dev.kobj, &gpio_keys_attr_group);return error; // 返回錯(cuò)誤碼
}
可以看出來(lái),分配、設(shè)置、注冊(cè)input_dev,然后去注冊(cè)中斷函數(shù),用于調(diào)用中斷的input_event函數(shù)上報(bào)中斷事件,嘗試寫一個(gè):
static struct input_dev *g_input_dev;
static int g_irq;
static irqreturn_t input_dev_demo_isr(int irq, void *dev_id)
{/* read data *//* report data */input_event(g_input_dev, EV_KEY, XX, 0);//通過(guò) input_event() 上報(bào)事件,input_sync() 用于同步報(bào)告的輸入事件。input_sync(g_input_dev);return IRQ_HANDLED;
}static int input_dev_demo_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;int error;struct resource *irq;/* 從設(shè)備樹中獲取硬件信息 *//* 分配、設(shè)置并注冊(cè) input_dev */g_input_dev = devm_input_allocate_device(dev);// 設(shè)置輸入設(shè)備的基本信息g_input_dev->name = "input_dev_demo";g_input_dev->phys = "input_dev_demo";g_input_dev->dev.parent = dev;g_input_dev->id.bustype = BUS_HOST;g_input_dev->id.vendor = 0x0001;g_input_dev->id.product = 0x0001;g_input_dev->id.version = 0x0100;/* 設(shè)置 1: 支持的事件類型 */__set_bit(EV_KEY, g_input_dev->evbit); // 鍵盤或按鈕事件__set_bit(EV_ABS, g_input_dev->evbit); // 絕對(duì)坐標(biāo)事件/* 設(shè)置 2: 支持的具體事件 */__set_bit(BTN_TOUCH, g_input_dev->keybit); // 觸摸按鈕事件__set_bit(ABS_MT_SLOT, g_input_dev->absbit); // 多點(diǎn)觸摸槽位__set_bit(ABS_MT_POSITION_X, g_input_dev->absbit); // 觸摸屏 X 軸坐標(biāo)__set_bit(ABS_MT_POSITION_Y, g_input_dev->absbit); // 觸摸屏 Y 軸坐標(biāo)/* 設(shè)置 3: 事件參數(shù) */input_set_abs_params(g_input_dev, ABS_MT_POSITION_X, 0, 0xffff, 0, 0); // X 坐標(biāo)范圍input_set_abs_params(g_input_dev, ABS_MT_POSITION_Y, 0, 0xffff, 0, 0); // Y 坐標(biāo)范圍// 注冊(cè)輸入設(shè)備error = input_register_device(g_input_dev);/* 硬件操作: 獲取中斷資源并注冊(cè)中斷 */irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);g_irq = irq->start;request_irq(irq->start, input_dev_demo_isr, IRQF_TRIGGER_RISING, "input_dev_demo_irq", NULL);return 0;
}
1.2 硬件相關(guān)操作
- 申請(qǐng)中斷
- 在中斷服務(wù)程序里
- 讀取硬件獲得數(shù)據(jù)
- 上報(bào)數(shù)據(jù)
void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value);static inline void input_sync(struct input_dev *dev); // 實(shí)質(zhì)也是 input_event
input子系統(tǒng)中讀取流程解析-CSDN博客
具體內(nèi)容在該章節(jié)的 event 處已經(jīng)講解過(guò)。
2. 代碼
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/sched.h>
#include <linux/pm.h>
#include <linux/slab.h>
#include <linux/sysctl.h>
#include <linux/proc_fs.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/input.h>
#include <linux/gpio_keys.h>
#include <linux/workqueue.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/spinlock.h>/* 定義指向輸入設(shè)備結(jié)構(gòu)體的指針 */
static struct input_dev *input_device_demo;
/* 用于保存輸入設(shè)備的中斷號(hào) */
static int irq_num;/* 中斷服務(wù)程序,處理輸入設(shè)備的事件 */
static irqreturn_t input_device_demo_isr(int irq, void *dev_id)
{/* 可在此處添加數(shù)據(jù)讀取和事件處理邏輯 *//* 向輸入子系統(tǒng)報(bào)告按鍵事件 */input_event(input_device_demo, EV_KEY, KEY_TOUCH, 0);input_sync(input_device_demo);return IRQ_HANDLED;
}/* 分配、配置和注冊(cè)平臺(tái)驅(qū)動(dòng) */
static int input_device_demo_probe(struct platform_device *pdev)
{struct device *dev = &pdev->dev;int error;struct resource *irq;/* 從設(shè)備樹獲取硬件信息 *//* 分配并初始化輸入設(shè)備結(jié)構(gòu)體 */input_device_demo = devm_input_allocate_device(dev);if (!input_device_demo)return -ENOMEM;/* 設(shè)置設(shè)備名稱和物理位置 */input_device_demo->name = "input_device_demo";input_device_demo->phys = "input_device_demo";input_device_demo->dev.parent = dev;/* 設(shè)置設(shè)備的總線類型和ID */input_device_demo->id.bustype = BUS_HOST;input_device_demo->id.vendor = 0x0001;input_device_demo->id.product = 0x0001;input_device_demo->id.version = 0x0100;/* 設(shè)置輸入設(shè)備支持的事件類型 */__set_bit(EV_KEY, input_device_demo->evbit); // 支持按鍵事件__set_bit(EV_ABS, input_device_demo->evbit); // 支持絕對(duì)位置事件/* 設(shè)置輸入設(shè)備支持的具體事件 */__set_bit(BTN_TOUCH, input_device_demo->keybit); // 支持觸摸按鍵事件__set_bit(ABS_MT_SLOT, input_device_demo->absbit); // 多點(diǎn)觸控槽位事件__set_bit(ABS_MT_POSITION_X, input_device_demo->absbit); // X軸位置事件__set_bit(ABS_MT_POSITION_Y, input_device_demo->absbit); // Y軸位置事件/* 設(shè)置具體事件的參數(shù)范圍,例如X和Y坐標(biāo)的最小值、最大值等 */input_set_abs_params(input_device_demo, ABS_MT_POSITION_X, 0, 0xffff, 0, 0);input_set_abs_params(input_device_demo, ABS_MT_POSITION_Y, 0, 0xffff, 0, 0);/* 注冊(cè)輸入設(shè)備 */error = input_register_device(input_device_demo);if (error)return error;/* 硬件操作:從設(shè)備樹獲取中斷資源并注冊(cè)中斷 */irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);irq_num = irq->start;error = request_irq(irq_num, input_device_demo_isr, IRQF_TRIGGER_RISING, "input_device_demo_irq", NULL);if (error)input_unregister_device(input_device_demo);return error;
}/* 驅(qū)動(dòng)移除函數(shù),釋放中斷資源并注銷輸入設(shè)備 */
static int input_device_demo_remove(struct platform_device *pdev)
{free_irq(irq_num, NULL);input_unregister_device(input_device_demo);return 0;
}/* 設(shè)備樹匹配表,用于匹配設(shè)備樹中描述的設(shè)備 */
static const struct of_device_id input_device_demo_of_match[] = {{ .compatible = "input,input_device_demo", },{ },
};/* 定義平臺(tái)驅(qū)動(dòng)結(jié)構(gòu)體,指定probe和remove函數(shù) */
static struct platform_driver input_device_demo_driver = {.probe = input_device_demo_probe,.remove = input_device_demo_remove,.driver = {.name = "input_device_demo",.of_match_table = input_device_demo_of_match,}
};/* 模塊初始化函數(shù),注冊(cè)平臺(tái)驅(qū)動(dòng) */
static int __init input_device_demo_init(void)
{return platform_driver_register(&input_device_demo_driver);
}/* 模塊退出函數(shù),注銷平臺(tái)驅(qū)動(dòng) */
static void __exit input_device_demo_exit(void)
{platform_driver_unregister(&input_device_demo_driver);
}module_init(input_device_demo_init);
module_exit(input_device_demo_exit);MODULE_LICENSE("GPL");
在以前的驅(qū)動(dòng)程序模板中,platform_driver中不僅注冊(cè)了file_operation等,也注冊(cè)了中斷處理函數(shù),并且中斷處理函數(shù)中是直接對(duì)中斷事件進(jìn)行處理
但是在內(nèi)核驅(qū)動(dòng)分層中,platform_input_dev,中斷處理函數(shù)只上報(bào)中斷事件給input.c,交由Input.c去調(diào)用input_dev對(duì)應(yīng)的input_handler中的函數(shù)來(lái)處理中斷(filter、events、event函數(shù)),同時(shí)input_handler層提供了app調(diào)用的接口函數(shù),如file_operation中的read