Maison > Article > Opération et maintenance > Le noyau Linux a-t-il une fonction d’interruption ?
Le noyau Linux a des fonctions d'interruption. Dans le noyau Linux, si vous souhaitez utiliser une interruption, vous devez en faire la demande, et la fonction request_irq() est utilisée pour demander une interruption. Une fois l'interruption utilisée, l'interruption correspondante doit être libérée via free_irq(. ) ; il existe également les fonctions enable_irq() et Disable_irq(), elles sont utilisées pour activer et désactiver les interruptions spécifiées.
L'environnement d'exploitation de ce tutoriel : système linux7.3, ordinateur Dell G3.
Fonction request_irq
Dans le noyau Linux, si vous souhaitez utiliser une interruption, vous devez en faire la demande. La fonction request_irq est utilisée. pour demander une interruption. La fonction request_irq peut provoquer une mise en veille, de sorte que la fonction request_irq ne peut pas être utilisée dans un contexte d'interruption ou dans d'autres sections de code qui interdisent la mise en veille. La fonction request_irq activera (activera) l'interruption, nous n'avons donc pas besoin d'activer manuellement l'interruption. Le prototype de la fonction request_irq est le suivant :
irq : Le numéro d'interruption à appliquer pour une interruption.
handler : Fonction de traitement d'interruption, cette fonction de traitement d'interruption sera exécutée lorsqu'une interruption se produit.
flags : indicateurs d'interruption, vous pouvez voir tous les indicateurs d'interruption dans le fichier include/linux/interrupt.h
name : nom de l'interruption, après réglage, vous pouvez voir le nom de l'interruption correspondant dans le /proc/interrupts déposer .
dev : Si les indicateurs sont définis sur IRQF_SHARED, dev est utilisé pour distinguer différentes interruptions. Généralement, dev est défini sur la structure du périphérique et dev sera transmis au deuxième paramètre de la fonction de gestion des interruptions irq_handler_t.
Valeur de retour : 0 signifie que l'application d'interruption est réussie, d'autres valeurs négatives signifient que l'application d'interruption échoue. Si -EBUSY est renvoyé, cela signifie que l'interruption a été demandée.
free_irq
Lors de l'utilisation d'une interruption, vous devez en faire la demande via la fonction request_irq. Une fois l'utilisation terminée, vous devez libérer l'interruption correspondante via la fonction free_irq. Si l'interruption n'est pas partagée, free_irq supprime le gestionnaire d'interruption et désactive l'interruption. Le prototype de la fonction free_irq est le suivant :
Les significations des paramètres de la fonction et des valeurs de retour sont les suivantes :
irq : L'interruption à libérer.
dev : Si l'interruption est définie sur partagée (IRQF_SHARED), ce paramètre est utilisé pour distinguer des interruptions spécifiques. Les interruptions partagées ne seront désactivées que lorsque le dernier gestionnaire d'interruption sera libéré.
Valeur de retour : Aucune.
Fonction de traitement d'interruption
Lorsque vous utilisez la fonction request_irq pour demander une interruption, vous devez définir la fonction de traitement d'interruption. Le format de la fonction de traitement d'interruption est le suivant :
Activation d'interruption et. désactiver les fonctions
Les fonctions d'utilisation et de désactivation des interruptions couramment utilisées sont les suivantes :
enable_irq et Disable_irq sont utilisées pour activer et désactiver les interruptions spécifiées, irq est le numéro d'interruption à désactiver. La fonction Disable_irq ne reviendra pas tant que la fonction de gestion des interruptions en cours d'exécution n'est pas terminée. Par conséquent, l'utilisateur doit s'assurer qu'aucune nouvelle interruption ne sera générée et que tous les gestionnaires d'interruptions qui ont commencé à s'exécuter sont terminés. Dans ce cas, vous pouvez utiliser une autre fonction de désactivation d'interruption :
disable_irq_nosync La fonction revient immédiatement après avoir été appelée et n'attend pas que le gestionnaire d'interruption actuel termine son exécution. Les trois fonctions ci-dessus activent ou désactivent toutes une certaine interruption. Parfois, nous devons désactiver l'ensemble du système d'interruption du processeur actuel, ce qui est souvent dit pour désactiver les interruptions globales lors de l'apprentissage de STM32. Dans ce cas, vous pouvez utiliser les deux suivantes. fonctions :
local_irq_enable est utilisé pour activer le système d'interruption actuel du processeur, et local_irq_disable est utilisé pour désactiver le système d'interruption actuel du processeur. Si la tâche A appelle local_irq_disable pour désactiver l'interruption globale pendant 10S, la tâche B commence à s'exécuter lorsqu'elle est désactivée pendant 2S. La tâche B appelle également local_irq_disable pour désactiver l'interruption globale pendant 3S. Après 3 secondes, la tâche B appelle la fonction local_irq_enable. pour activer l'interruption globale. À ce moment-là, 2+3=5 secondes se sont écoulées, puis l'interruption globale a été activée. À ce moment-là, le désir de la tâche A de désactiver l'interruption globale pendant 10 secondes a été brisé, puis la tâche A est devenue « en colère », et les conséquences étaient graves, il est possible que le système soit complètement crashé par la tâche A. Afin de résoudre ce problème, la tâche B ne peut pas simplement et grossièrement activer l'interruption globale via la fonction local_irq_enable, mais restaurer l'état d'interruption à l'état précédent. En tenant compte des sentiments des autres tâches, les deux fonctions suivantes doivent être utilisées. à ce moment. :
Dans certains matériaux, la moitié supérieure et la moitié inférieure sont également appelées moitié supérieure et moitié inférieure, qui ont la même signification. La fonction de service d'interruption que nous avons enregistrée lors de l'utilisation de request_irq pour demander une interruption appartient à la moitié supérieure du traitement d'interruption. Tant que l'interruption est déclenchée, la fonction de traitement d'interruption sera exécutée. Nous savons tous que la fonction de traitement des interruptions doit être exécutée le plus rapidement possible, et plus elle est courte, mieux c'est, mais la réalité est souvent cruelle. Certains processus de traitement des interruptions prennent plus de temps et nous devons les traiter pour réduire le temps d'exécution. la fonction de traitement des interruptions. Par exemple, l'écran tactile capacitif informe le SOC qu'un événement tactile se produit via une interruption. Le SOC répond à l'interruption, puis lit la valeur des coordonnées tactiles via l'interface IIC et la signale au système. Mais nous savons tous que la vitesse maximale d'IIC n'est que de 400 Kbit/S, donc lire des données via IIC pendant une interruption fera perdre du temps. Nous pouvons exécuter temporairement l'opération de lecture des données tactiles via IIC, et la fonction de traitement des interruptions ne répond qu'à l'interruption, puis efface le bit de l'indicateur d'interruption. À l'heure actuelle, le processus de traitement des interruptions est divisé en deux parties :
Moitié supérieure : La moitié supérieure est la fonction de traitement des interruptions. Les processus de traitement qui sont plus rapides et ne prennent pas beaucoup de temps peuvent être terminés dans la moitié supérieure.
Partie inférieure : si le processus de traitement des interruptions prend du temps, ces codes fastidieux sont émis et transmis à la partie inférieure pour exécution, afin que la fonction de traitement des interruptions entre et sorte rapidement.
Par conséquent, l'objectif principal du noyau Linux divisant les interruptions en moitiés supérieure et inférieure est d'obtenir une entrée et une sortie rapides de la fonction de traitement des interruptions. Ces opérations d'exécution rapides et sensibles au temps peuvent être placées dans la fonction de traitement des interruptions. Moitié supérieure. Tout le travail restant peut être exécuté dans la moitié inférieure. Par exemple, les données sont copiées dans la mémoire dans la moitié supérieure et le traitement spécifique des données peut être exécuté dans la moitié inférieure. Quant à savoir quels codes appartiennent à la moitié supérieure et quels codes appartiennent à la moitié inférieure, il n'existe pas de réglementation claire. Tous les jugements sont basés sur l'utilisation réelle. Cela permettra de tester les compétences des rédacteurs de pilotes. Voici quelques points de référence dont vous pouvez tirer des leçons :
① Si le contenu à traiter ne veut pas être interrompu par d'autres interruptions, alors il peut être placé dans la moitié supérieure.
② Si la tâche à traiter est urgente, vous pouvez la placer dans la moitié supérieure.
③. Si les tâches à traiter sont liées au matériel, elles peuvent être placées dans la moitié supérieure
④ Pour les tâches autres que les trois points ci-dessus, la priorité doit être placée dans la moitié inférieure.
Demi-mécanisme inférieur :
Interruption douce
Au début, le noyau Linux fournissait un mécanisme de « moitié inférieure » pour implémenter la moitié inférieure, appelé « BH ». Plus tard, des interruptions logicielles et des tasklets ont été introduits pour remplacer le mécanisme « BH ». Les interruptions logicielles et les tasklets peuvent être utilisés pour remplacer BH. À partir de la version 2.5 du noyau Linux, BH a été abandonné. Le noyau Linux utilise la structure softirq_action pour représenter les interruptions logicielles. La structure softirq_action est définie dans le fichier include/linux/interrupt.h. Le contenu est le suivant :
Un total de 10 interruptions logicielles sont définies dans le noyau/softirq. .c, comme indiqué ci-dessous :
NR_SOFTIRQS est un type d'énumération, défini dans le fichier include/linux/interrupt.h, et est défini comme suit :
On peut voir qu'il y a 10 interruptions logicielles au total , donc NR_SOFTIRQS vaut 10, donc le tableau softirq_vec a 10 éléments. La variable membre d'action dans la structure softirq_action est la fonction de service de l'interruption logicielle. Le tableau softirq_vec est un tableau global, donc tous les processeurs (pour les systèmes SMP) peuvent y accéder. Chaque processeur a son propre mécanisme de déclenchement et de contrôle, et s'exécute uniquement. interruptions douces déclenchées par lui-même. Cependant, les fonctions de service d'interruption logicielle exécutées par chaque CPU sont en effet les mêmes, et ce sont toutes des fonctions d'action définies dans le tableau softirq_vec. Pour utiliser des interruptions logicielles, vous devez d'abord utiliser la fonction open_softirq pour enregistrer la fonction de traitement des interruptions logicielles correspondante. Le prototype de la fonction open_softirq est le suivant :
nr : Pour que l'interruption logicielle soit activée, sélectionnez-en une dans le fichier. exemple de code 51.1.2.3.
action : La fonction de traitement correspondant à l'interruption logicielle.
Valeur de retour : Aucune valeur de retour.
Après avoir enregistré l'interruption logicielle, elle doit être déclenchée via la fonction raise_softirq. Le prototype de la fonction raise_softirq est le suivant :
L'interruption logicielle doit être enregistrée statiquement lors de la compilation ! Le noyau Linux utilise la fonction softirq_init pour initialiser softirqs. La fonction softirq_init est définie dans le fichier kernel/softirq.c. Le contenu de la fonction est le suivant :
tasklet
tasklet est un autre mécanisme de la moitié inférieure implémenté à l'aide de softirqs. . Entre les interruptions logicielles et les tasklets, il est recommandé d'utiliser des tasklets. La fonction func à la ligne 489 de la structure du noyau Linux
est la fonction de traitement à exécuter par la tasklet. Le contenu de la fonction définie par l'utilisateur est équivalent à la fonction de traitement des interruptions. Si vous souhaitez utiliser une tasklet, vous devez d'abord définir une tasklet, puis utiliser la fonction tasklet_init pour initialiser la tasklet. Le prototype de la fonction taskled_init est le suivant :
La signification des paramètres de la fonction et des valeurs de retour. sont les suivants :
t : La tasklet à initialiser
func : Fonction de traitement de la Tasklet.
data : Paramètres à transmettre à la fonction func
Valeur de retour : Aucune valeur de retour.
Vous pouvez également utiliser la macro DECLARE_TASKLET pour terminer la définition et l'initialisation de la tasklet en une seule fois. DECLARE_TASKLET est défini dans le fichier include/linux/interrupt.h et est défini comme suit :
où name est le nom de la tasklet. à définir, qui est un type tasklet_struct Lorsque des variables sont utilisées, func est la fonction de traitement de la tasklet et data est le paramètre transmis à la fonction func.
Dans la partie supérieure, c'est-à-dire que l'appel de la fonction tasklet_schedule dans la fonction de gestion des interruptions peut faire exécuter la tasklet au moment approprié. Le prototype de la fonction tasklet_schedule est le suivant :
L'exemple d'utilisation de référence de tasklet est le suivant. :
File d'attente des travaux
La file d'attente de travail est une autre méthode d'exécution de la moitié inférieure. La file d'attente de travail s'exécute dans le contexte du processus, le travail est transféré à un thread du noyau pour exécution. la file d'attente permet de dormir ou de replanifier. Par conséquent, si vous souhaitez reporter le travail qui peut dormir, vous pouvez choisir la file d'attente de travail, sinon vous ne pouvez choisir que des interruptions logicielles ou des tâches.
Le noyau Linux utilise la structure work_struct pour représenter un travail, le contenu est le suivant (la compilation conditionnelle est omise) :
Ces travaux sont organisés en files d'attente de travail, et la file d'attente de travail est représentée par la structure workqueue_struct, le contenu est comme suit (la compilation conditionnelle est omise) :
Le noyau Linux utilise des threads de travail pour traiter chaque tâche dans la file d'attente de travail. Le noyau Linux utilise la structure de travail pour représenter le thread de travail.
Comme le montre l'exemple de code 51.1.2.10, chaque travailleur a une file d'attente de travail et le thread de travail traite tout le travail dans sa propre file d'attente de travail. Dans le développement réel du pilote, il nous suffit de définir le travail (work_struct), et nous n'avons pratiquement pas à nous soucier de la file d'attente de travail et des threads de travail. Créer simplement une œuvre est très simple, il suffit de définir une variable de structure work_struct, puis d'utiliser la macro INIT_WORK pour initialiser l'œuvre. La macro INIT_WORK est définie comme suit :
Si vous utilisez l'arborescence des périphériques, vous devez définir les informations sur les attributs d'interruption dans l'arborescence des périphériques. Le noyau Linux configure les interruptions en lisant les informations sur les attributs d'interruption dans l'arborescence des périphériques. Pour les contrôleurs d'interruption, reportez-vous au document Documentation/devicetree/bindings/arm/gic.txt pour obtenir des informations sur la liaison de l'arborescence des périphériques. Ouvrez le fichier imx6ull.dtsi. Le nœud intc est le nœud du contrôleur d'interruption de I.MX6ULL. Le contenu du nœud est le suivant :
Ligne 2, la valeur de l'attribut compatible est "arm, cortex-a7-gic" sous Linux. Code source du noyau Recherchez "arm,cortex-a7-gic" pour trouver le fichier du pilote du contrôleur d'interruption GIC.
Ligne 3, #interrupt-cells est identique à #address-cells, #size-cells. Indique la taille des cellules du périphérique sous ce contrôleur d'interruption. Pour le périphérique, l'attribut interruptions est utilisé pour décrire les informations d'interruption. #interrupt-cells décrit la taille des cellules de l'attribut interruptions, c'est-à-dire le nombre de cellules présentes pour une. message. Chaque cellule est une valeur entière de 32 bits. Pour le GIC traité par ARM, il y a 3 cellules au total. La signification de ces trois cellules est la suivante :
Premières cellules : type d'interruption, 0 signifie interruption SPI, 1 signifie interruption PPI. .
La deuxième cellule : numéro d'interruption. Pour les interruptions SPI, la plage des numéros d'interruption est de 0 à 987. Pour les interruptions PPI, la plage des numéros d'interruption est de 0 à 15.
La troisième cellule : flag, bit[3:0] indique le type de déclenchement d'interruption. Lorsqu'il est à 1, il indique un déclenchement sur front montant, lorsqu'il est à 2, il indique un déclenchement sur front descendant, lorsqu'il est à 4, il indique. un déclencheur de niveau haut, et lorsqu'il est 8, il indique un déclencheur de niveau haut. Lorsqu'il indique un déclencheur de niveau bas. bit[15:8] est le masque CPU de l'interruption PPI.
À la ligne 4, le nœud du contrôleur d'interruption est vide, indiquant que le nœud actuel est le contrôleur d'interruption.
Pour gpio, le nœud gpio peut également être utilisé comme contrôleur d'interruption. Par exemple, le contenu du nœud gpio5 dans le fichier imx6ull.dtsi est le suivant :
À la ligne 4, les interruptions décrivent les informations sur la source d'interruption. gpio5, il y en a deux au total, les types d'interruption sont tous SPI et les niveaux de déclenchement sont IRQ_TYPE_LEVEL_HIGH. La différence réside dans la source d'interruption, l'une est 74 et l'autre 75. Ouvrez le chapitre « Chapitre 3 Interruptions et événements DMA » du « Manuel de référence IMX6ULL » et recherchez le tableau 3-1, comme indiqué dans la figure 50.1.3.1 :
Comme le montre la figure 50.1.3.1, GPIO5 utilise un total de 2 numéros d'interruption, l'un est 74 et l'autre est 75. Parmi eux, 74 correspond aux 16 IO inférieurs de GPIO5_IO00~GPIO5_IO15, et 75 correspond aux 16 IO supérieurs de GPIO5_IO16~GPIOI5_IO31. Ligne 8, interruption-controller indique que le nœud gpio5 est également un contrôleur d'interruption, utilisé pour contrôler toutes les interruptions IO de gpio5.
Ligne 9, remplacez #interrupt-cells par 2.
Ouvrez le fichier imx6ull-alientek-emmc.dts et recherchez le contenu suivant :
La signification des paramètres de la fonction et de la valeur de retour est. comme suit :
dev : nœud de périphérique.
index : Numéro d'index. L'attribut d'interruption peut contenir plusieurs informations d'interruption. Utilisez l'index pour spécifier les informations à obtenir.
Valeur de retour : numéro d'interruption. Si vous utilisez GPIO, vous pouvez utiliser la fonction gpio_to_irq pour obtenir le numéro d'interruption correspondant à gpio. Le prototype de la fonction est le suivant :
#include <linux/types.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/ide.h> #include <linux/slab.h> #include <linux/uaccess.h> #include <linux/io.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/of_gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/of_irq.h> #include <asm/mach/map.h> #include <linux/timer.h> #include <linux/jiffies.h> #define IMX6UIRQ_CNT 1 /* 设备号个数 */ #define IMX6UIRQ_NAME "irqDev" /* 名字 */ #define KEY0VALUE 0X01 /* KEY0 按键值 */ #define INVAKEY 0XFF /* 无效的按键值 */ #define KEY_NUM 1 /* 按键数量 */ /* 可能会有好多按键,通过结构体数组来描述按键 */ /* 中断 IO 描述结构体 */ struct irq_keydesc { int gpio; /* gpio */ int irqnum; /* 中断号 */ unsigned char value; /* 按键对应的键值 */ char name[10]; /* 名字 */ irqreturn_t (*handler)(int, void *); /* 中断服务函数 */ }; /* irq设备结构体 */ struct imx6uirq_dev { dev_t devid; /* 设备号 */ struct cdev cdev; /* 字符设备 */ struct class *class; /* 类 */ struct device *device; /* 设备 */ int major; /* 注设备号 */ int minor; /* 次设备号 */ struct device_node *nd; /* 设备节点 */ atomic_t keyvalue; /* 有效的按键键值 */ atomic_t releasekey; /* 标记是否完成一次完成的按键*/ struct timer_list timer; /* 定义一个定时器*/ struct irq_keydesc irqkeydesc[KEY_NUM]; /* 按键描述数组 */ unsigned char curkeynum; /* 当前的按键号 */ }; struct imx6uirq_dev irqDev; /* 定义LED结构体 */ /* @description : 中断服务函数,开启定时器,延时 10ms, * 定时器用于按键消抖。 * 两个参数是中断处理函数的必须写法 * @param - irq : 中断号 * @param - dev_id : 设备结构。 * @return : 中断执行结果 */ static irqreturn_t key0_handler(int irq, void *dev_id) { struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id; /* 采用定时器削抖,如果再定时器时间内还是这个值,说明是真的按下了,在定时器中断中处理 */ /* 这里设置为0是简易处理,因为只有一个按键 */ /* 有其他按键要再建一个中断处理函数,并把curkeynum改成相应的按键值 */ /* 注意不能所有按键用一个中断函数,第一是一起按的时候会出错 */ /* 第二,无法用curkeynum判断使用的是第几个按键 */ dev->curkeynum = 0; /* 传递给定时器的参数,注意要强转,在中断处理函数里面再转回来 */ dev->timer.data = (volatile long)dev_id; /* mod_timer会启动定时器,第二个参数是要修改的超时时间 */ mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); return IRQ_RETVAL(IRQ_HANDLED); } /* @description : 定时器服务函数,用于按键消抖,定时器到了以后 * 再次读取按键值,如果按键还是处于按下状态就表示按键有效。 * @param – arg : 设备结构变量 * @return : 无 */ void timer_function(unsigned long arg) { unsigned char value; unsigned char num; struct irq_keydesc *keydesc; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg; /* 因为只有一个按键,这里是0 */ num = dev->curkeynum; keydesc = &dev->irqkeydesc[num]; value = gpio_get_value(keydesc->gpio); /* 读取 IO 值 */ if(value == 0){ /* 按下按键 */ atomic_set(&dev->keyvalue, keydesc->value); } else{ /* 按键松开 */ /* 这种情况是按下再松开的松开,使用keyValue加上releaseKey */ /* 没按下的话, releasekey一直为0*/ atomic_set(&dev->keyvalue, 0x80 | keydesc->value); atomic_set(&dev->releasekey, 1); /* 标记松开按键 */ } } /* * @description : 按键 IO 初始化 * @param : 无 * @return : 无 */ static int keyio_init(void) { unsigned char i = 0; int ret = 0; /* 1.获取key节点 */ irqDev.nd = of_find_node_by_path("/key"); if (irqDev.nd== NULL){ printk("key node not find!\r\n"); return -EINVAL; } /* 对每个按键都提取 GPIO */ for (i = 0; i < KEY_NUM; i++) { irqDev.irqkeydesc[i].gpio = of_get_named_gpio(irqDev.nd, "key-gpios", i); if (irqDev.irqkeydesc[i].gpio < 0) { printk("can't get key%d\r\n", i); } } /* 初始化 key 所使用的 IO,并且设置成中断模式 */ for (i = 0; i < KEY_NUM; i++) { /* 先对每一个IO命名 */ /* 先对命名清0 */ memset(irqDev.irqkeydesc[i].name, 0, sizeof(irqDev.irqkeydesc[i].name)); /* 给IO命名 */ sprintf(irqDev.irqkeydesc[i].name, "KEY%d", i); /* 请求GPIO */ gpio_request(irqDev.irqkeydesc[i].gpio, irqDev.irqkeydesc[i].name); /* 设置GPIO为输入 */ gpio_direction_input(irqDev.irqkeydesc[i].gpio); /* 获取中断号,以下为两个方法,都可以获取到 */ /* 从interrupts属性里面获取 */ /* 注意i是根据设备树里面设置了多少个就是多少个,都会获取到 */ /* 下面的方法是通用的获取中断号的函数 */ irqDev.irqkeydesc[i].irqnum = irq_of_parse_and_map(irqDev.nd, i); #if 0 /* 此方法是gpio获取中断号的方法 */ irqDev.irqkeydesc[i].irqnum = gpio_to_irq(irqDev.irqkeydesc[i].gpio); #endif printk("key%d:gpio=%d, irqnum=%d\r\n", i, irqDev.irqkeydesc[i].gpio, irqDev.irqkeydesc[i].irqnum); } /* 2. 按键中断初始化 */ /* 设置中断处理函数和按键初始值 */ /* 因为只有一个key0.,所以这里也没用循环 */ irqDev.irqkeydesc[0].handler = key0_handler; irqDev.irqkeydesc[0].value = KEY0VALUE; /* 申请中断 */ for (i = 0; i < KEY_NUM; i++) { /* request_irq参数 * 中断号,中断函数,中断触发类型,中断名字,传递给中断处理函数的参数(第二个),这里传的结构体 * */ ret = request_irq(irqDev.irqkeydesc[i].irqnum, irqDev.irqkeydesc[i].handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, irqDev.irqkeydesc[i].name, &irqDev); if(ret < 0){ printk("irq %d request failed!\r\n", irqDev.irqkeydesc[i].irqnum); return -EFAULT; } } /* 3. 创建定时器 */ init_timer(&irqDev.timer); irqDev.timer.function = timer_function; /* 注意下面不能让定时器运行,因为要按下按键之后再运行 */ /* 启动定时器通过mod_timer启动,通常在初始化阶段的定时器用的是add_timer */ return 0; } static int imx6uirq_open(struct inode *inode, struct file *filp) { filp->private_data = &irqDev; return 0; } static int imx6uirq_release(struct inode *inode, struct file *filp) { //struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; return 0; } /* * @description : 从设备读取数据 * @param – filp : 要打开的设备文件(文件描述符) * @param – buf : 返回给用户空间的数据缓冲区 * @param - cnt : 要读取的数据长度 * @param – offt : 相对于文件首地址的偏移 * @return : 读取的字节数,如果为负值,表示读取失败 */ static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { int ret = 0; unsigned char keyvalue = 0; /* 按键值 */ unsigned char releasekey = 0; /* 标记是否一次完成 */ struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data; keyvalue = atomic_read(&dev->keyvalue); releasekey = atomic_read(&dev->releasekey); if (releasekey) { /* 有按键按下 */ if (keyvalue & 0x80) { keyvalue &= ~0x80; /* 因为中断中或了一个0x80,这里面去掉0x80 */ ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue)); } else { goto data_error; } atomic_set(&dev->releasekey, 0); /* 按下标志清零 */ } else { /* 没有按下 */ goto data_error; } return 0; data_error: return -EINVAL; } /* 字符设备操作集 */ static const struct file_operations imx6uirq_fops = { .owner = THIS_MODULE, .open = imx6uirq_open, .release = imx6uirq_release, .read = imx6uirq_read }; /* 模块入口函数 */ static int __init imx6uirq_init(void) { /* 定义一些所需变量 */ int ret = 0; /* 1. 注册字符设备驱动 */ irqDev.major = 0; if(irqDev.major) { irqDev.devid = MKDEV(irqDev.major, 0); ret = register_chrdev_region(irqDev.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME ); } else { alloc_chrdev_region(&irqDev.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME ); irqDev.major = MAJOR(irqDev.devid); irqDev.minor = MINOR(irqDev.devid); } if(ret < 0){ goto fail_devid; } printk("Make devid success! \r\n"); printk("major = %d, minor = %d \r\n", irqDev.major, irqDev.minor); /* 2. 初始化cdev */ irqDev.cdev.owner = THIS_MODULE; cdev_init(&irqDev.cdev, &imx6uirq_fops); ret = cdev_add(&irqDev.cdev, irqDev.devid, IMX6UIRQ_CNT); if (ret < 0){ goto fail_cdev; } else { printk("Cdev add sucess! \r\n"); } /* 3. 自动创建设备节点 */ irqDev.class = class_create(THIS_MODULE, IMX6UIRQ_NAME ); if(IS_ERR(irqDev.class)) { ret = PTR_ERR(irqDev.class); goto fail_class; } else { printk("Class create sucess! \r\n"); } irqDev.device = device_create(irqDev.class, NULL, irqDev.devid, NULL, IMX6UIRQ_NAME ); if(IS_ERR(irqDev.device)) { ret = PTR_ERR(irqDev.device); goto fail_device; } else { printk("Device create sucess! \r\n"); } /* 4.初始化按键 */ atomic_set(&irqDev.keyvalue, INVAKEY); atomic_set(&irqDev.releasekey, 0); keyio_init(); printk("irqDev init! \r\n"); return 0; /* 错误处理 */ fail_device: class_destroy(irqDev.class); fail_class: cdev_del(&irqDev.cdev); fail_cdev: unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT); fail_devid: return ret; } /* 模块出口函数 */ static void __exit imx6uirq_exit(void) { unsigned int i = 0; /* 删除定时器 */ del_timer_sync(&irqDev.timer); /* 释放中断 */ for (i = 0; i < KEY_NUM; i++) { free_irq(irqDev.irqkeydesc[i].irqnum, &irqDev); } /* 1. 释放设备号 */ cdev_del(&irqDev.cdev); /* 2. 注销设备号 */ unregister_chrdev_region(irqDev.devid, IMX6UIRQ_CNT); /* 3. 摧毁设备 */ device_destroy(irqDev.class, irqDev.devid); /* 4.摧毁类 */ class_destroy(irqDev.class); printk("irqDev exit! \r\n"); } /* 模块入口和出口注册 */ module_init(imx6uirq_init); module_exit(imx6uirq_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Shao Zheming");
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include "linux/ioctl.h" /* * argc: 应用程序参数个数 * argv[]: 参数是什么,具体的参数,说明参数是字符串的形式 * .chrdevbaseApp <filename> <0:1> 0表示关灯,1表示开灯 * .chrdevbaseApp /dev/led 0 关灯 * .chrdevbaseApp /dev/led 1 开灯 * */ int main(int argc, char *argv[]) { if(argc != 2) { printf("Error Usage!\r\n"); return -1; } int fd, ret; char *filename; unsigned char data; filename = argv[1]; fd = open(filename, O_RDWR); if(fd < 0) { printf("file %s open failed! \r\n", filename); return -1; } while (1) { ret = read(fd, &data, sizeof(data)); if (ret < 0) { /* 数据读取错误或者无效 */ } else { /* 数据读取正确 */ if (data) /* 读取到数据 */ printf("key value = %#X\r\n", data); } } close(fd); return 0; }
Recommandations associées : "
Tutoriel vidéo Linux"
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!