嵌入式Linux驅動開發:i.MX6ULL按鍵中斷驅動(非阻塞IO)

嵌入式Linux驅動開發:i.MX6ULL按鍵中斷驅動(非阻塞IO)

概述

本文檔詳細介紹了在i.MX6ULL開發板上實現按鍵中斷驅動的完整過程。該驅動程序實現了非阻塞IO操作,允許用戶空間應用程序通過poll系統調用高效地監控按鍵狀態變化,而無需進行忙等待。本文檔結合了提供的源代碼和設備樹文件,詳細解釋了驅動程序的各個組成部分及其工作原理。

源碼倉庫

  • 倉庫地址: https://gitee.com/dream-cometrue/linux_driver_imx6ull

理論基礎

1. 非阻塞IO (Non-blocking I/O)

在傳統的阻塞IO模型中,當應用程序調用read等系統調用時,如果數據不可用,進程會進入睡眠狀態,直到數據就緒。這在某些場景下是高效的,但在需要同時監控多個文件描述符或進行其他工作的場景下,會導致資源浪費。

非阻塞IO通過在open系統調用時指定O_NONBLOCK標志來實現。在這種模式下,如果read調用時沒有數據可讀,系統調用會立即返回一個-EAGAIN錯誤,而不是讓進程睡眠。這允許應用程序立即處理其他任務,或者使用pollselect系統調用來監控多個文件描述符的狀態。

2. poll系統調用

poll系統調用是實現非阻塞IO的核心機制。它允許應用程序在一個系統調用中監控多個文件描述符的讀、寫和異常事件。poll會阻塞直到任何一個被監控的文件描述符就緒,或者超時。

在驅動程序中,poll操作通過file_operations結構體中的.poll成員函數實現。驅動程序需要調用poll_wait函數將當前進程添加到一個等待隊列中,并返回一個描述當前文件描述符狀態的掩碼。

3. 等待隊列 (Wait Queue)

等待隊列是Linux內核中用于進程同步的機制。它允許一個或多個進程在某個條件滿足之前進入睡眠狀態。當條件滿足時,另一個進程或中斷處理程序可以喚醒等待隊列中的所有進程。

在本驅動程序中,我們使用等待隊列來實現poll功能。當應用程序調用poll時,驅動程序會將當前進程添加到等待隊列中。當按鍵狀態發生變化時,中斷處理程序會通過定時器喚醒等待隊列中的進程。

4. 定時器 (Timer)

定時器用于在指定的時間后執行一段代碼。在本驅動程序中,定時器用于實現按鍵消抖。當按鍵中斷發生時,我們啟動一個20ms的定時器。在定時器超時后,我們讀取按鍵的實際狀態,以避免由于機械按鍵的抖動導致的誤觸發。

5. 原子變量 (Atomic Variables)

原子變量是內核中用于在多處理器系統中實現無鎖同步的機制。對原子變量的操作是不可分割的,保證了在并發訪問時的數據一致性。在本驅動程序中,我們使用原子變量keyvaluerelease來在中斷上下文和進程上下文之間安全地傳遞按鍵值和釋放狀態。

設備樹 (Device Tree)

設備樹文件imx6ull-alientek-emmc.dts定義了開發板上的硬件配置。與本驅動程序相關的部分是/key節點:

key{compatible = "alientek,key";pinctrl-names = "default";pinctrl-0 = <&pinctrl_key>;states = "okay";key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;interrupt-parent = <&gpio1>;interrupts = <18 IRQ_TYPE_EDGE_BOTH>;
};
  • compatible = "alientek,key": 指定了該節點的兼容性字符串,驅動程序會根據這個字符串來匹配設備。
  • pinctrl-0 = <&pinctrl_key>: 引用了iomuxc節點中的pinctrl_key子節點,用于配置GPIO1_IO18引腳的復用功能和電氣特性。
  • key-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>: 指定了按鍵連接到gpio1控制器的第18號引腳,且為高電平有效。
  • interrupt-parent = <&gpio1>: 指定了中斷控制器為gpio1
  • interrupts = <18 IRQ_TYPE_EDGE_BOTH>: 指定了中斷號為18,觸發類型為上升沿和下降沿(雙邊沿觸發)。

在驅動程序中,我們通過of_find_node_by_path("/key")找到這個節點,并通過of_get_named_gpio函數獲取按鍵的GPIO號。

驅動程序分析

1. 數據結構

struct key_desc

該結構體用于描述一個按鍵設備:

struct key_desc
{char name[10];        // 按鍵名稱int gpio;             // GPIO號int irqnum;           // 中斷號unsigned char value;  // 按鍵值irqreturn_t (*handler)(int, void *); // 中斷處理函數
};
struct imx6uirq_dev

該結構體是驅動程序的核心,包含了所有必要的狀態信息:

struct imx6uirq_dev
{dev_t devid;                    // 設備號int major;                      // 主設備號int minor;                      // 次設備號struct cdev cdev;               // 字符設備struct class *class;            // 設備類struct device *device;          // 設備struct device_node *key_nd;     // 設備樹節點struct key_desc key[KEY_NUM];   // 按鍵描述數組struct timer_list timer;        // 定時器atomic_t keyvalue;              // 按鍵值(原子變量)atomic_t release;               // 釋放狀態(原子變量)wait_queue_head_t r_wait;       // 讀等待隊列
};

2. 字符設備操作

imx6uirq_open

該函數在應用程序打開設備文件時被調用。它將private_data指針指向imx6uirq全局變量,以便后續操作可以訪問驅動程序的狀態。

static int imx6uirq_open(struct inode *inode, struct file *filp)
{filp->private_data = &imx6uirq;return 0;
}
imx6uirq_release

該函數在應用程序關閉設備文件時被調用。在本驅動程序中,它不執行任何特定操作。

static int imx6uirq_release(struct inode *inode, struct file *filp)
{return 0;
}
imx6uirq_read

該函數實現了read系統調用。它根據O_NONBLOCK標志決定是阻塞還是非阻塞讀取。

  • 如果指定了O_NONBLOCK標志,且沒有按鍵釋放事件,則立即返回-EAGAIN
  • 否則,調用wait_event_interruptible將進程添加到等待隊列中,直到有按鍵釋放事件發生。
  • 一旦有事件發生,將按鍵值復制到用戶空間緩沖區,并重置釋放狀態。
ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{struct imx6uirq_dev *dev = filp->private_data;u8 keyvalue, release;int ret = 0;if (filp->f_flags & O_NONBLOCK){if (atomic_read(&dev->release) == 0){return -EAGAIN;}}else{wait_event_interruptible(dev->r_wait, atomic_read(&dev->release));}keyvalue = atomic_read(&dev->keyvalue);release = atomic_read(&dev->release);if (release){ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));if (ret){ret = -EFAULT;goto fail_copy_user;}atomic_set(&dev->release, 0);}else{ret = -EAGAIN;goto fail_key_unrelease;}return sizeof(keyvalue);
}
imx6uirq_poll

該函數實現了poll系統調用。它調用poll_wait將當前進程添加到r_wait等待隊列中,并檢查release原子變量。如果release為真,則返回POLLIN | POLLRDNORM,表示文件描述符可讀。

static unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{int mask = 0;struct imx6uirq_dev *dev = filp->private_data;poll_wait(filp, &dev->r_wait, wait);if (atomic_read(&dev->release)){mask = POLLIN | POLLRDNORM;}return mask;
}

3. 中斷處理

key0_handler

該函數是按鍵中斷的處理程序。它在按鍵狀態發生變化時被調用。由于中斷處理程序執行在中斷上下文中,不能進行睡眠操作,因此我們不能在這里直接讀取GPIO狀態。相反,我們啟動一個定時器,在定時器的回調函數中讀取GPIO狀態。

static irqreturn_t key0_handler(int irq, void *filp)
{struct imx6uirq_dev *dev = filp;dev->timer.data = (volatile long)filp;mod_timer(&dev->timer, jiffies + msecs_to_jiffies(20));return IRQ_HANDLED;
}
timer_func

該函數是定時器的回調函數。它在定時器超時后被調用。在本函數中,我們讀取按鍵的GPIO狀態:

  • 如果按鍵被按下(GPIO為低電平),則設置keyvalueKEY0VALUE
  • 如果按鍵被釋放(GPIO為高電平),則設置release為1,并喚醒等待隊列中的所有進程。
static void timer_func(unsigned long arg)
{struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;int value = 0;value = gpio_get_value(dev->key[0].gpio);if (value == 0){atomic_set(&dev->keyvalue, dev->key[0].value);}else{atomic_set(&dev->release, 1);}if (atomic_read(&dev->release)){wake_up_interruptible(&dev->r_wait);}
}

4. 驅動程序初始化和退出

imx6uirq_init

該函數在模塊加載時被調用。它完成了以下初始化工作:

  1. 分配設備號(動態或靜態)。
  2. 初始化字符設備,并將其添加到系統中。
  3. 創建設備類和設備文件。
  4. 調用key_init1函數初始化按鍵。
  5. 初始化定時器。
  6. 初始化原子變量和等待隊列。
static int __init imx6uirq_init(void)
{// ... (設備號分配、字符設備注冊、設備類和設備創建)ret = key_init1(&imx6uirq);if (ret < 0){goto fail_key_init;}timer1_init(&imx6uirq);atomic_set(&imx6uirq.keyvalue, KEYINVA);atomic_set(&imx6uirq.release, 0);init_waitqueue_head(&imx6uirq.r_wait);return 0;
}
key_init1

該函數負責初始化按鍵硬件。它通過設備樹API獲取按鍵的GPIO號,并請求GPIO、配置為輸入模式,然后獲取中斷號并注冊中斷處理程序。

int key_init1(struct imx6uirq_dev *dev)
{u8 ret = 0, i = 0;dev->key_nd = of_find_node_by_path("/key");if (dev->key_nd == NULL){ret = -EFAULT;goto fail_find_nd;}dev->key[i].handler = key0_handler;dev->key[i].value = KEY0VALUE;for (i = 0; i < KEY_NUM; i++){dev->key[i].gpio = of_get_named_gpio(dev->key_nd, "key-gpios", i);if (dev->key[i].gpio < 0){ret = -EFAULT;goto fail_get_gpio;}memset(dev->key[i].name, 0, sizeof(dev->key[i].name));sprintf(dev->key[i].name, "KEY%d", i);ret = gpio_request(dev->key[i].gpio, dev->key[i].name);if (ret){ret = -EFAULT;goto fail_gpio_req;}ret = gpio_direction_input(dev->key[i].gpio);if (ret){ret = -EFAULT;goto fail_gpio_dir;}dev->key[i].irqnum = gpio_to_irq(dev->key[i].gpio);ret = request_irq(dev->key[i].irqnum, dev->key[i].handler, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, dev->key[i].name, &imx6uirq);if (ret){ret = -EFAULT;goto fail_req_irq;}}return 0;
}
timer1_init

該函數初始化定時器,設置其回調函數為timer_func

void timer1_init(struct imx6uirq_dev *dev)
{init_timer(&dev->timer);dev->timer.function = timer_func;
}
imx6uirq_exit

該函數在模塊卸載時被調用。它負責釋放所有分配的資源,包括中斷、GPIO、設備文件、字符設備、設備類和設備號。

static void __exit imx6uirq_exit(void)
{u8 i = 0;for (i = 0; i < KEY_NUM; i++){free_irq(imx6uirq.key[i].irqnum, &imx6uirq);gpio_free(imx6uirq.key[i].gpio);}del_timer(&imx6uirq.timer);device_destroy(imx6uirq.class, imx6uirq.devid);class_destroy(imx6uirq.class);cdev_del(&imx6uirq.cdev);unregister_chrdev(imx6uirq.major, IMX6UIRQ_NAME);
}

用戶空間應用程序

用戶空間應用程序imx6uirqAPP.c演示了如何使用poll系統調用來監控按鍵狀態。

1. poll的使用

應用程序創建一個pollfd結構體,指定要監控的文件描述符、感興趣的事件(POLLIN)和返回的事件。然后調用poll函數,指定超時時間為500毫秒。

struct pollfd fds;
fds.fd = fd;
fds.events = POLLIN;
ret = poll(&fds, 1, 500);

2. 事件處理

poll返回后,應用程序檢查revents字段以確定發生了什么事件。如果POLLIN事件發生,則調用read函數讀取按鍵值。

if (ret > 0)
{if (fds.revents | POLLIN){unsigned char ch = 0;int ret = read(fd, &ch, sizeof(ch));if (ret >= 0){if (ch == KEY0VALUE){printf("User: key is pressing, ret is: %d\r\n", ret);}}}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/95508.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/95508.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/95508.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

從 @Schedule 到 XXL-JOB:分布式定時任務的演進與實踐

從Schedule到XXL-JOB&#xff1a;分布式定時任務的演進與實踐 在分布式系統中&#xff0c;定時任務是常見需求&#xff08;如數據備份、報表生成、緩存刷新等&#xff09;。Spring框架的Schedule注解雖簡單易用&#xff0c;但在集群環境下存在明顯局限&#xff1b;而XXL-JOB作為…

阿里云營業執照OCR接口的PHP實現與技術解析:從簽名機制到企業級應用

一、阿里云營業執照OCR接口的核心技術架構 阿里云OCR服務基于深度學習模型和大規模數據訓練,針對中國營業執照的版式特征(如統一社會信用代碼位置、企業名稱排版、經營范圍換行規則等)進行了專項優化,識別準確率可達98%以上。其接口調用遵循RESTful API設計規范,采用HMAC…

AI人工智能大模型應用如何落地

AI人工智能大模型應用落地需要經過以下步驟&#xff1a; 明確應用場景和目標&#xff1a;首先需要明確AI大模型在哪個領域、解決什么問題。例如&#xff0c;在智能客服領域&#xff0c;AI大模型可以用于提高客戶服務的效率和質量&#xff1b;在醫學領域&#xff0c;AI大模型可以…

手寫Muduo網絡庫核心代碼2--Poller、EPollPoller詳細講解

Poller抽象層代碼Muduo 網絡庫中的 Poller 抽象層是其事件驅動模型的核心組件之一&#xff0c;負責統一封裝不同 I/O 復用機制&#xff08;如 epoll、poll&#xff09;&#xff0c;實現事件監聽與分發。Poller 抽象層的作用統一 I/O 復用接口Poller 作為抽象基類&#xff0c;定…

基于MCP架構的OpenWeather API服務端設計與實現

隨著微服務和模塊化架構的發展&#xff0c;越來越多的系統傾向于采用可插拔、高內聚的設計模式。MCP(Modular, Collaborative,Pluggable)架構正是這樣一種強調模塊化、協作性和擴展性的設計思想。它允許開發者以“組件”方式組合功能&#xff0c;提升系統的靈活性與可維護性。 …

從“疊加”到“重疊”:Overlay 與 Overlap 雙引擎驅動技術性能優化

在技術領域&#xff0c;“Overlay”和“Overlap”常因拼寫相似被混淆&#xff0c;但二者實則代表兩種截然不同的優化邏輯&#xff1a;Overlay 是“主動構建分層結構”&#xff0c;通過資源復用與隔離提升效率&#xff1b;Overlap 是“讓耗時環節時間交叉”&#xff0c;通過并行…

【Vue2 ?】 Vue2 入門之旅(六):指令與過濾器

前一篇我們學習了組件化開發。本篇將介紹 指令與過濾器&#xff0c;這是 Vue 模板語法的重要擴展&#xff0c;讓頁面渲染更加靈活。 目錄 常見內置指令自定義指令過濾器小結 常見內置指令 Vue 提供了豐富的內置指令&#xff0c;常見的有&#xff1a; <div id"app&qu…

【隨筆】【Debian】【ArchLinux】基于Debian和ArchLinux的ISO鏡像和虛擬機VM的系統鏡像獲取安裝

一、Debian Debian -- Debian 全球鏡像站 阿里巴巴開源鏡像站-OPSX鏡像站-阿里云開發者社區 debian-cd-current-amd64-iso-cd安裝包下載_開源鏡像站-阿里云 清華源&#xff1a; 清華大學開源軟件鏡像站 | Tsinghua Open Source Mirror USTC Open Source Software Mirror 二、…

如何用 Kotlin 在 Android 手機開發一個文字游戲,并加入付費機制?

Kotlin 開發 Android 文字游戲基礎框架使用 Android Studio 創建項目&#xff0c;選擇 Kotlin 作為主要語言。基礎游戲邏輯可通過狀態機和文本解析實現&#xff1a;class GameEngine {private var currentScene: Scene loadStartingScene()fun processCommand(input: String):…

安卓開發---BaseAdapter(定制ListView的界面)

概念&#xff1a;BaseAdapter 是 Android 中最基礎的適配器類&#xff0c;它是所有其他適配器&#xff08;如 ArrayAdapter、SimpleAdapter&#xff09;的父類。方法簽名&#xff1a;public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { // 獲取數據…

Conda配置完全指南:Windows系統Anaconda/Miniconda的安裝、配置、基礎使用、清理緩存空間和Pycharm/VSCode配置指南

本文同步發布在個人博客&#xff1a; Conda配置完全指南Conda 是一個開源的跨平臺包管理與環境管理工具&#xff0c;廣泛應用于數據科學、機器學習及 Python 開發領域。它不僅能幫助用戶快速安裝、更新和卸載第三方庫&#xff0c;還能創建相互隔離的虛擬環境&#xff0c;解決不…

什么是賬號矩陣?如何避免賬號IP關聯風險

賬號矩陣是指在同一平臺或多個平臺上&#xff0c;圍繞同一品牌、業務或個人 IP 構建的多個相互關聯、協同運作的賬號體系。這些賬號通過差異化的內容定位和運營策略形成互補&#xff0c;共同實現流量聚合、品牌曝光或業務拓展的目標。協同效應&#xff1a;賬號間通過內容互推、…

解析ELK(filebeat+logstash+elasticsearch+kibana)日志系統原理以及k8s集群日志采集過程

ELK日志系統解析 ELK 日志系統&#xff08;現常稱為 Elastic Stack&#xff0c;由 Filebeat、Logstash、Elasticsearch、Kibana 組成&#xff09;是一套用于 日志收集、清洗、存儲、檢索和可視化 的開源解決方案。 它的核心價值是將分散在多臺服務器 / 應用中的日志 “匯聚成池…

python 內置函數 sort() 復雜度分析筆記

在做 280. 擺動排序 時&#xff0c;有一版 python 題解&#xff0c;里面直接用了sort() &#xff0c;又用了一個簡單的 for 循環&#xff0c;但整體時間復雜度為 O(n?log(n)) &#xff0c;那么問題就出自這個 sort() &#xff0c;所以在這分析一下 sort() 的復雜度。Python 的…

【光照】Unity中的[經驗模型]

【從UnityURP開始探索游戲渲染】專欄-直達 圖形學第一定律&#xff1a;“看起來對就對” URP光照模型發展史 ?2018年?&#xff1a;URP首次發布&#xff08;原LWRP&#xff09;&#xff0c;繼承傳統前向渲染的Blinn-Phong簡化版?2019年?&#xff1a;URP 7.x引入Basic Shade…

uniapp小程序使用自定義底部tabbar,并根據用戶類型動態切換tabbar數據

1.注意點 在pages.json中配置tabbar如下字段&#xff1a;custom&#xff1a;true &#xff0c;會自動隱藏原生tabbar&#xff0c;使用自定義的tabbar2.如何自定義呢 可以使用第三方組件庫的tabbar組件&#xff0c;然后二次封裝下內部封裝邏輯&#xff1a; 1.點擊切換邏輯 2.根據…

Redis 哨兵 (基于 Docker)

目錄 1. 基本概念 2. 安裝部署 (基于 Docker) 2.1 使用 docker 獲取 redis 鏡像 2.2 編排 主從節點 2.3 編排 redis-sentinel 節點 3. 重新選舉 4. 選舉原理 5. 總結 1. 基本概念 名詞 邏輯結構物理結構主節點Reids 主服務一個獨立的 redis-server 進程從節點Redis 從…

Python學習-day4

Python 語言的運算符: 算術運算符比較&#xff08;關系&#xff09;運算符賦值運算符邏輯運算符位運算符成員運算符身份運算符運算符優先級 算術運算符 定義變量a 21&#xff0c;變量b 10。運算符描述實例加 - 兩個對象相加a b 輸出結果 31-減 - 得到負數或是一個數減去另一…

Vite 插件 @vitejs/plugin-legacy 深度解析:舊瀏覽器兼容指南

&#x1f4d6; 簡介 vitejs/plugin-legacy 是 Vite 官方提供的兼容性插件&#xff0c;專門用于為現代瀏覽器構建的應用程序提供對舊版瀏覽器的支持。該插件通過自動生成兼容性代碼和 polyfill&#xff0c;確保您的應用能夠在 IE 11 等舊版瀏覽器中正常運行。 核心價值 向后兼…

數據質檢之springboot通過yarn調用spark作業實現數據質量檢測

Spring Boot 應用中通過 YARN 來調用 Spark 作業的來執行數據質檢。這是一個非常經典的數據質量檢測、數據優化的常用架構,將Web服務/業務處理(Spring Boot)與大數據質檢(Spark on YARN)解耦。 核心架構圖 首先,通過一張圖來理解整個流程的架構: 整個流程的核心在于,…