嵌入式Linux设备驱动程序开发指南12(处理设备驱动中使用的中断)——读书笔记

十二、处理设备驱动中使用的中断

12.1 Linux内核的中断域

IRQ是来自设备的中断请求,不同设备可以共享中断线,从而可以共享IRQ。中断控制器驱动将irq_chip数据结构注册到内核中,

GPIO中断芯片分类:
1、级联GPIO中断芯片

generic_handle_irq()	//运行每个特定GPIO设备驱动处理程序
request_irq()			//请求此特定的GPIO控制器中断引脚
platform_get_resource()	//从设备树中提取与源于父级中断控制器的中断引脚对应每个中断号

2、嵌套线程化GPIO中断芯片
片外GPIO扩展器以及位于睡眠总线另一侧的任何其他GPIO中断芯片。

gpiochip_irqchip_add()
irq_create_mapping()

12.2 设备树中断处理

中断信号可以来自机器中的任何设备,也可以在机器设备的任何设备上终止,设备寻址表现为树形结构,中断信号表现为独立于树的节点之间的连接。
中断连接的4个属性:
1、interrupt_controller属性,将节点表明为接受中断连接;
2、interrupt_cell是中断控制节点的属性,表明设备中断属性中的单元数;
3、interrupt_parent包含与其相连的中断控制器的phandle;
4、interrupt包含一系列中断说明符

举例:

			flx4: flexcom@fc018000 {
				atmel,flexcom-mode = <ATMEL_FLEXCOM_MODE_TWI>;
				status = "okay";

				i2c2: i2c@600 {
					compatible = "atmel,sama5d2-i2c";
					reg = <0x600 0x200>;
					interrupts = <23 IRQ_TYPE_LEVEL_HIGH 7>;
					dmas = <0>, <0>;
					dma-names = "tx", "rx";
					#address-cells = <1>;
					#size-cells = <0>;
					clocks = <&flx4_clk>;
					pinctrl-names = "default";
					pinctrl-0 = <&pinctrl_flx4_default>;
					atmel,fifo-size = <16>;
					status = "okay";
				};
			};

12.3 按钮中断设备

中断由CPU调度,异步运行。在Linux中,使用以下函数来获取和释放中断的:

request_irq()
free_irq()

分配中断资源、并启用中断线和IRQ处理函数是用的函数:

devm_request_irq()

中断处理函数的定义如下:

irqreturn_t (*handler)(int irq_no, void *dev_id);	    //中断处理函数接收Linux中断号(irq_no)

12.3.1 设备树

			pinctrl@fc038000 {
				/*
				 * There is no real pinmux for ADC, if the pin
				 * is not requested by another peripheral then
				 * the muxing is done when channel is enabled.
				 * Requesting pins for ADC is GPIO is
				 * encouraged to prevent conflicts and to
				 * disable bias in order to be in the same
				 * state when the pin is not muxed to the adc.
				 */
				pinctrl_adc_default: adc_default {
					pinmux = <PIN_PD23__GPIO>;
					bias-disable;
				};
				...
					
					pinctrl_key_gpio_default: key_gpio_default {
					pinmux = <PIN_PB9__GPIO>;
					bias-pull-up;
				};

/ {
	model = "Atmel SAMA5D2 Xplained";
	compatible = "atmel,sama5d2-xplained", "atmel,sama5d2", "atmel,sama5";

	chosen {
		stdout-path = "serial0:115200n8";
	};

	clocks {
		slow_xtal {
			clock-frequency = <32768>;
		};

		main_xtal {
			clock-frequency = <12000000>;
		};
	};

	int_key{
		compatible = "arrow,intkey";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_key_gpio_default>;
		gpios = <&pioA 41 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&pioA>;
		interrupts = <41  IRQ_TYPE_EDGE_FALLING>;
	};

12.3.2 int_sam_key.c代码

/* 头文件 */
#include <linux/module.h>
#include <linux/platform_device.h> 	
#include <linux/interrupt.h> 		
#include <linux/gpio/consumer.h>
#include <linux/miscdevice.h>

static char *HELLO_KEYS_NAME = "PB_KEY";

/* interrupt handler */
//编写中断处理函数
static irqreturn_t hello_keys_isr(int irq, void *data)
{
	struct device *dev = data;
	dev_info(dev, "interrupt received. key: %s\n", HELLO_KEYS_NAME);
	return IRQ_HANDLED;
}

static struct miscdevice helloworld_miscdevice = {
		.minor = MISC_DYNAMIC_MINOR,
		.name = "mydev",
};

static int __init my_probe(struct platform_device *pdev)
{	
	int ret_val, irq;
	struct gpio_desc *gpio;
	struct device *dev = &pdev->dev;

	dev_info(dev, "my_probe() function is called.\n");
	
	/* First method to get the Linux IRQ number */
	//获取设备中断号,获取中断号有两种:
	// devm_gpiod_get()
	// platform_get_irq()
	gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);
	if (IS_ERR(gpio)) {
		dev_err(dev, "gpio get failed\n");
		return PTR_ERR(gpio);
	}
	irq = gpiod_to_irq(gpio);
	if (irq < 0)
		return irq;
	dev_info(dev, "The IRQ number is: %d\n", irq);

	/* Second method to get the Linux IRQ number */
	irq = platform_get_irq(pdev, 0);
	if (irq < 0){
		dev_err(dev, "irq is not available\n");
		return -EINVAL;
	}
	dev_info(dev, "IRQ_using_platform_get_irq: %d\n", irq);
	
	/* Allocate the interrupt line */
	//分配中断线
	//irq是中断号
	//hello_keys_isr
	ret_val = devm_request_irq(dev, irq, hello_keys_isr, 
				   IRQF_TRIGGER_FALLING,
				   HELLO_KEYS_NAME, dev);
	if (ret_val) {
		dev_err(dev, "Failed to request interrupt %d, error %d\n", irq, ret_val);
		return ret_val;
	}
	
	ret_val = misc_register(&helloworld_miscdevice);
	if (ret_val != 0)
	{
		dev_err(dev, "could not register the misc device mydev\n");
		return ret_val;
	}

	dev_info(dev, "mydev: got minor %i\n",helloworld_miscdevice.minor);
	dev_info(dev, "my_probe() function is exited.\n");

	return 0;
}

static int __exit my_remove(struct platform_device *pdev)
{	
	dev_info(&pdev->dev, "my_remove() function is called.\n");
	misc_deregister(&helloworld_miscdevice);
	dev_info(&pdev->dev, "my_remove() function is exited.\n");
	return 0;
}

//声明驱动程序支持的设备列表
static const struct of_device_id my_of_ids[] = {
	{ .compatible = "arrow,intkey"},
	{},
};

MODULE_DEVICE_TABLE(of, my_of_ids);

//注册平台总线
static struct platform_driver my_platform_driver = {
	.probe = my_probe,
	.remove = my_remove,
	.driver = {
		.name = "intkey",
		.of_match_table = my_of_ids,
		.owner = THIS_MODULE,
	}
};

module_platform_driver(my_platform_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a button INT platform driver");

12.3.3 测试调试

insmod int_key_imx_key.ko
cat /proc/interrupts
rmmod int_key_imx_key.ko

12.4 延迟工作

延迟工作是一种内核机制,允许开发人员将代码安排在以后的时间执行。这些延迟后的所执行的代码可以使用工作队列或线程化中断的进程上下文运行,也可以在软中断、tasklet和定时器的中断上下文中运行。延迟工作具有补充中断处理的功能,因为中断是具有严格的要求和限制,如:中断处理程序的执行时间必须尽可能短,在中断上下文中,不能出现可能导致阻塞的调用。
1、进程上下文:在执行过程中允许被阻塞的被执行的系统调用,由于工作队列和中断调度的延迟工作也视为在进程上下文中执行。这些内核线程在内核态的上下文中执行,不代表任何用户进程。
2、中断上下文:响应硬件中断请求的异步执行,有被称为原子上下文。中断的产生是不可预期的,它们产生并执行中断的处理程序。软中断、tasklet和定时器都在中断上下文中运行,这意味着它们无法调用阻塞函数。

请添加图片描述
在中断延迟工作机制下,中断处理函数以最短的时间处理完之后,从中断处理程序安排一个异步操作,稍后延迟运行,完成剩下的操作,中断处理使用的这种延迟操作被称作是下半部。其目的是完成中断处理程序即上半部中剩余的动作。
下半部的中断处理在Linux中通过软中断、tasklet、线程化的中断在上下文中实现。

12.4.1 软中断

适用于运行时间长、非阻塞的处理程序。软中断会尽早执行,但任然可以被中断,一边任何中断处理程序即上半部抢占它。
设备驱动程序不能使用软中断,软中断是为内核子系统保留的,对于当前内核版本定义了一下类型:

enum
{
   HI_SOFTIRQ=0,		//最高优先级,不能调用阻塞函数,运行tasklet
 
   TIMER_SOFTIRQ,		//运行定时器
 
   NET_TX_SOFTIRQ,		//被网络子系统使用
 
   NET_RX_SOFTIRQ,		//被网络子系统使用
 
   BLOCK_SOFTIRQ,		//被IO子系统使用
 
   BLOCK_IOPOLL_SOFTIRQ,	//被IO子系统使用,使用iopoll处理程序时提高性能
 
   TASKLET_SOFTIRQ,			//运行tasklet
 
   SCHED_SOFTIRQ,			//用于调度子系统使用,实现负载均衡
 
   HRTIMER_SOFTIRQ,			//高精度定时器使用
 
   RCU_SOFTIRQ,  			//Preferable RCU should always be the last softirq
 
   NR_SOFTIRQS
 
};

12.4.2 tasklet

tasklet是延时工作的一种特殊形式,在中断上下文中运行,tasklet可以动态分配,因此可以运行在设备驱动程序。

tasklet_init()					//tasklet初始化
tasklet_schedule()		//tasklet调度
tasklet_hi_schedule()	//只有HI_SOFTIRQ类型软中断被调度

12.4.3 定时器

定时器是一种经常被使用的延迟工作处理方式。他们在中断上下文运行,并在软中断之山实现。

struct timer_list *timer;
setup_timer()	//初始化定时器
mod_timer()		//调度定时器

del_timer()
del_timer_sync()	

定时器一个常见错误是忘记关闭定时器,删除模块之前,必须停止定时器。如果在删除模块之前没有停止定时器,定时器到期,则无法将处理函数加载到内核中,并将产内核oops。

12.4.4 线程化的中断

处理中断的驱动程序无法保证所以场景都在非阻塞的模式下读取设备寄存器,如果是阻塞的,就可以使用内核提供了线程化的中断,使用函数如下:

request_threaded_irq()    //完成中断上下文和进程上下文节点,运行的中断处理函数
devm_request_threaded_irq()	//建议使用

12.4.5 工作队列

工作队列是延迟工作的另一种方式。工作的基本单位是work,并在工作队列中排队。工作队列将工作延迟到运行于进程上下文的内核线程中,并且可以睡眠。工作线程由工作线程池控制,工作线程池负责并发(同时运行的工作线程)级别管理和进程管理。

#include <linux/workquene.h>

alloc_workqueue()
schedule_work()
schedule_delayed_work()

12.8 内核中的锁

12.8.1 简介

解决并发问题访问临界区(竞争)的方案是,使用锁。锁确保只有一个实例可以随时进入临界区。
内核锁有两种形式:

1、自旋锁:include/asm/spinlock.h,
如果无法获得自旋锁,则将一直尝试自旋直到获得锁(禁止正在运行的内核中的抢占);

2、互斥锁:include/linux/mutex.h,
如果无法获得自旋锁,则任务将被自动挂起,并在互斥锁释放时被唤醒,意味着CPU在等待互斥锁的期间,可以执行其他操作。

12.8.2 锁的使用场景

1、锁和单处理器内核:连个配置CONFIG)sMP和CONFIG_PREEMPT编译开关,是自旋锁的开关,决定自旋锁是否禁止抢占;
2、在用户上下文中使用锁:使用简单的互斥锁实现只从用户上下文中访问的数据结构的保护,mutex_lock_interruptible()获取互斥锁,mutex_unlock()释放互斥锁。
3、在中断和进程上下文之间共享自旋锁:

spain_lock_irqsave()
spain_unlock_irqrestore()

12.8.3 keyled_sam_class.c代码(keyled完整代码)

使用共享自旋锁来保护中断和上下文,

//包含头文件
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h> 	
#include <linux/property.h>
#include <linux/kthread.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/spinlock.h>

#define LED_NAME_LEN		32
#define INT_NUMBER		2
static const char *HELLO_KEYS_NAME1 = "PB_KEY";
static const char *HELLO_KEYS_NAME2 = "MIKROBUS_KEY";

/* Specific LED private structure */
//创建私有数据结构,
struct led_device {
	char name[LED_NAME_LEN];  //设备名称
	struct gpio_desc *ledd;	  //三个LED连接的gpio描述符  即/* each LED gpio_desc */
	struct device *dev; 
	struct keyled_priv *private; /* pointer to the global private struct */
};

/* Global private structure */
struct keyled_priv {
	u32 num_leds;  //LED数量
	u8 led_flag;   //是否有某个LED被点亮
	u8 task_flag;  //通知你是否正在运行内核线程
	u32 period;		//保存闪烁周期
	spinlock_t period_lock;		//保存闪烁周期
	struct task_struct *task; 	/* kthread task_struct */
	struct class *led_class;  	/* the keyled class */
	struct device *dev;
	dev_t led_devt;		  	/* first device identifier */
	struct led_device *leds[];	/* pointers to each led private struct */
};

/* kthread function */
//撰写的线程函数
static int led_flash(void *data){
	unsigned long flags;
	u32 value = 0;
	struct led_device *led_dev = data;
	dev_info(led_dev->dev, "Task started\n");
	dev_info(led_dev->dev, "I am inside the kthread\n");
	while(!kthread_should_stop()) {
		spin_lock_irqsave(&led_dev->private->period_lock, flags);
		u32 period = led_dev->private->period;
		spin_unlock_irqrestore(&led_dev->private->period_lock, flags);
		value = !value;
		gpiod_set_value(led_dev->ledd, value);
		msleep(period/2);
	}
	gpiod_set_value(led_dev->ledd, 0); /* switch off the led */
	dev_info(led_dev->dev, "Task completed\n");
	return 0;
};

/*
 * sysfs methods
 */

/* switch on/of each led */
//接收用户态的on/off参数
static ssize_t set_led_store(struct device *dev,
		struct device_attribute *attr,
		const char *buf, size_t count)
{
	int i;
	char *buffer = buf;
	struct led_device *led_count;
	//获取LED device数据结构
	struct led_device *led = dev_get_drvdata(dev);

	/* replace \n added from terminal with \0 */
	*(buffer+(count-1)) = '\0';

	if (led->private->task_flag == 1) {
			kthread_stop(led->private->task);
			led->private->task_flag = 0;
	}

	if(!strcmp(buffer, "on")) {
		if (led->private->led_flag == 1) {
			for (i = 0; i < led->private->num_leds; i++) {
				led_count = led->private->leds[i];
				gpiod_set_value(led_count->ledd, 0);
			}
		gpiod_set_value(led->ledd, 1);
		}
		else {
			gpiod_set_value(led->ledd, 1);
			led->private->led_flag = 1;
		}
	}
	else if (!strcmp(buffer, "off")) {
		gpiod_set_value(led->ledd, 0);
	}
	else {
		dev_info(led->dev, "Bad led value.\n");
		return -EINVAL;
	}

	return count;
}
static DEVICE_ATTR_WO(set_led);

/* blinking ON the specific LED running a kthread */
//函数将仅从用户态接收“on”参数
//首先,所以LED都将关闭
//然后,如果没有任何内核线程运行,它将启动一个新的线程,该LED将在特定的时间段内闪烁
//如果已经有一个内核线程在运行了,则该函数将退出
static ssize_t blink_on_led_store(struct device *dev,
		struct device_attribute *attr,
		const char *buf, size_t count)
{
	int i;
	char *buffer = buf;
	struct led_device *led_count;
	struct led_device *led = dev_get_drvdata(dev);

	/* replace \n added from terminal with \0 */
	*(buffer+(count-1)) = '\0';

	if (led->private->led_flag == 1) { 
		for (i = 0; i < led->private->num_leds; i++) {
			led_count = led->private->leds[i];
			gpiod_set_value(led_count->ledd, 0); 
		}
	}

	if(!strcmp(buffer, "on")) {
		if (led->private->task_flag == 0)
		{
			led->private->task = kthread_run(led_flash, led, "Led_flash_tread");
			if(IS_ERR(led->private->task)) {
				dev_info(led->dev, "Failed to create the task\n");
				return PTR_ERR(led->private->task);
			}
		}
		else
			return -EBUSY;
	}
	else {
		dev_info(led->dev, "Bad led value.\n");
		return -EINVAL;
	}

	led->private->task_flag = 1;

	dev_info(led->dev, "Blink_on_led exited\n");
	return count;
}
static DEVICE_ATTR_WO(blink_on_led);

/* switch off the blinking of any led */
//
static ssize_t blink_off_led_store(struct device *dev,
		struct device_attribute *attr,
		const char *buf, size_t count)
{
	int i;
	char *buffer = buf;
	struct led_device *led = dev_get_drvdata(dev);
	struct led_device *led_count;

	/* replace \n added from terminal with \0 */
	*(buffer+(count-1)) = '\0';

	if(!strcmp(buffer, "off")) {
		if (led->private->task_flag == 1) {
			kthread_stop(led->private->task);
			for (i = 0; i < led->private->num_leds; i++) {
				led_count = led->private->leds[i];
				gpiod_set_value(led_count->ledd, 0);
			}
		}
		else
			return 0;
	}
	else {
		dev_info(led->dev, "Bad led value.\n");
		return -EINVAL;
	}

	led->private->task_flag = 0;
	return count;
}
static DEVICE_ATTR_WO(blink_off_led);

/* set the blinking period */
//设置新的闪烁周期
static ssize_t set_period_store(struct device *dev,
				struct device_attribute *attr,
				const char *buf, size_t count)
{
	unsigned long flags;
	int ret, period;
	struct led_device *led = dev_get_drvdata(dev);
	dev_info(led->dev, "Enter set_period\n");

	ret = sscanf(buf, "%u", &period);
	if (ret < 1 || period < 10 || period > 10000) {
		dev_err(dev, "invalid value\n");
		return -EINVAL;
	}

	spin_lock_irqsave(&led->private->period_lock, flags);
	led->private->period = period;
	spin_unlock_irqrestore(&led->private->period_lock, flags);

	dev_info(led->dev, "period is set\n");
	return count;
}
static DEVICE_ATTR_WO(set_period);

/* Declare the sysfs structures */
static struct attribute *led_attrs[] = {
        &dev_attr_set_led.attr,
	&dev_attr_blink_on_led.attr,
	&dev_attr_blink_off_led.attr,
        &dev_attr_set_period.attr,
        NULL,
};

static const struct attribute_group led_group = {
        .attrs = led_attrs,
};

static const struct attribute_group *led_groups[] = {
        &led_group,
        NULL,
};

/* 
 * Allocate space for the global private struct
 * and the three local LED private structs
 */
static inline int sizeof_keyled_priv(int num_leds)
{
	return sizeof(struct keyled_priv) +
			(sizeof(struct led_device*) * num_leds);
}


/* First interrupt handler */
//中断服务程序
static irqreturn_t KEY_ISR1(int irq, void *data)
{
	struct keyled_priv *priv = data;
	dev_info(priv->dev, "interrupt PB_KEY received. key: %s\n",
			 HELLO_KEYS_NAME1);

	priv->period = priv->period + 10;
	if ((priv->period < 10) || (priv->period > 10000))
		priv->period = 10;
	dev_info(priv->dev, "the led period is %d\n", priv->period);

	return IRQ_HANDLED;
}

/* Second interrupt handler */
static irqreturn_t KEY_ISR2(int irq, void *data)
{
	struct keyled_priv *priv = data;
	dev_info(priv->dev, "interrupt MIKROBUS_KEY received. key: %s\n",
			 HELLO_KEYS_NAME2);

	priv->period = priv->period - 10;
	if ((priv->period < 10) || (priv->period > 10000))
		priv->period = 10;
	dev_info(priv->dev, "the led period is %d\n", priv->period);

	return IRQ_HANDLED;
}

/* Create the LED devices under the sysfs keyled entry */
struct led_device *led_device_register(const char *name, int count,
		struct device *parent, dev_t led_devt, struct class *led_class)
{
	struct led_device *led;
	dev_t devt;
	int ret;

	/* First allocate a new led device */
	led = devm_kzalloc(parent, sizeof(struct led_device), GFP_KERNEL);
	if (!led)
		return ERR_PTR(-ENOMEM);

	/* Get the minor number of each device */
	devt = MKDEV(MAJOR(led_devt), count);

	/* Create the device and init the device's data */
	led->dev = device_create(led_class, parent, devt, 
				 led,"%s", name); 
	if (IS_ERR(led->dev)) {
		dev_err(led->dev, "unable to create device %s\n", name);
		ret = PTR_ERR(led->dev);
		return ERR_PTR(ret);
	}

	dev_info(led->dev, "the major number is %d\n", MAJOR(led_devt));
	dev_info(led->dev, "the minor number is %d\n", MINOR(devt));

	/* To recover later from each sysfs entry */
	//在每个LED设备和led_device数据结构之间进行关联
	dev_set_drvdata(led->dev, led);

	strncpy(led->name, name, LED_NAME_LEN);

	dev_info(led->dev, "led %s added\n", led->name);

	return led;
}

static int __init my_probe(struct platform_device *pdev)
{	
	int count, ret, i;
	unsigned int major;
	struct fwnode_handle *child;

	struct device *dev = &pdev->dev;
	struct keyled_priv *priv;

	dev_info(dev, "my_probe() function is called.\n");

	//获取LED和中断设备的数量
	count = device_get_child_node_count(dev);
	if (!count)
		return -ENODEV;

	dev_info(dev, "there are %d nodes\n", count);

	/* Allocate all the private structures */
	//分配专用数据结构,为这个全家数据结构分配空间
	priv = devm_kzalloc(dev, sizeof_keyled_priv(count-INT_NUMBER), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	/* Allocate 3 device numbers */
	//分配三个设备编号
	alloc_chrdev_region(&priv->led_devt, 0, count-INT_NUMBER, "Keyled_class");
	major = MAJOR(priv->led_devt);
	dev_info(dev, "the major number is %d\n", major);

	/* Create the LED class */
	priv->led_class = class_create(THIS_MODULE, "keyled");
	if (!priv->led_class) {
		dev_info(dev, "failed to allocate class\n");
		return -ENOMEM;
	}

	/* Set attributes for this class */
	priv->led_class->dev_groups = led_groups;
	priv->dev = dev;
	
	//初始化自旋锁
	//自旋锁用于保护对在中断上下文和用户上下文之间的共享的period变量的访问
	//1.对于SMP体系,如i.MX7D和BCM2837 :将在用户上下文中使用spin_lock_irqsave()和中断服务程序中使用spin_lock()
	//2.然而,对于单核处理器,如:SAMA5D2,则不需要在中断服务程序调用spin_lock(),
	//因为在单核处理器中,用户上下文中已经获取自旋锁的处理器来说,中断服务程序无法在另一个处理器中执行。
	spin_lock_init(&priv->period_lock);

	/* Parse all the DT nodes */
	//为找到的每个LED设备,去创建的/sys/class/keyled/
	device_for_each_child_node(dev, child){

		int irq, flags;
		struct gpio_desc *keyd;
		const char *label_name, *colour_name, *trigger;
		struct led_device *new_led;

		fwnode_property_read_string(child, "label", &label_name);

		if (strcmp(label_name,"led") == 0) {

			fwnode_property_read_string(child, "colour", &colour_name);

			/*
			 * Create led devices under keyled class
                         * priv->num_leds is 0 for the first iteration
                         * used to set the minor number of each device
                         * increased to the end of the iteration
                         */
			new_led = led_device_register(colour_name, priv->num_leds, dev,
						      priv->led_devt, priv->led_class);
			if (!new_led) {
				fwnode_handle_put(child);
				ret = PTR_ERR(new_led);

				for (i = 0; i < priv->num_leds-1; i++) {
					device_destroy(priv->led_class, MKDEV(MAJOR(priv->led_devt), i));
				}
				class_destroy(priv->led_class);
				return ret;
			}

			new_led->ledd = devm_get_gpiod_from_child(dev, NULL, child);
			if (IS_ERR(new_led->ledd)) {
				fwnode_handle_put(child);
			    ret = PTR_ERR(new_led->ledd);
			    goto error;
			}
			new_led->private = priv;
			priv->leds[priv->num_leds] = new_led;
			priv->num_leds++;

			/* set direction to output */
			//将引脚方向设置为输出
			gpiod_direction_output(new_led->ledd, 1);

			/* 
			 * set led state to off,
			 * the output is high as
			 * in DT the gpio is declared ACTIVE:LOW
			 */
			gpiod_set_value(new_led->ledd, 0);
		}

		/* Parsing the interrupt nodes */
		else if (strcmp(label_name,"PB_KEY") == 0) {
			keyd = devm_get_gpiod_from_child(dev, NULL, child);
			gpiod_direction_input(keyd);
			fwnode_property_read_string(child, "trigger", &trigger);
			if (strcmp(trigger, "falling") == 0)
				flags = IRQF_TRIGGER_FALLING;
			else if (strcmp(trigger, "rising") == 0)
				flags = IRQF_TRIGGER_RISING;
			else if (strcmp(trigger, "both") == 0)
				flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
			else
				return -EINVAL;
				
			//获取Linux IRQ号
			irq = gpiod_to_irq(keyd);
			if (irq < 0)
				return irq;
				
			//分配两个中断
			ret = devm_request_irq(dev, irq, KEY_ISR1, 
					       flags, "ISR1", priv);
			if (ret) {
				dev_err(dev, "Failed to request interrupt %d, error %d\n", irq, ret);
				return ret;
			}
			dev_info(dev, "IRQ number: %d\n", irq);
		}

		else if (strcmp(label_name,"MIKROBUS_KEY") == 0) {

			keyd = devm_get_gpiod_from_child(dev, NULL, child);
			gpiod_direction_input(keyd);
			fwnode_property_read_string(child, "trigger", &trigger);
			if (strcmp(trigger, "falling") == 0)
				flags = IRQF_TRIGGER_FALLING;
			else if (strcmp(trigger, "rising") == 0)
				flags = IRQF_TRIGGER_RISING;
			else if (strcmp(trigger, "both") == 0)
				flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING;
			else
				return -EINVAL;

			irq = gpiod_to_irq(keyd);
			if (irq < 0)
				return irq;

			ret = devm_request_irq(dev, irq, KEY_ISR2, 
					       flags, "ISR2", priv);
			if (ret < 0) {
				dev_err(dev, "Failed to request interrupt %d, error %d\n", irq, ret);
				goto error;
			}
			dev_info(dev, "IRQ number: %d\n", irq);
		}

		else {
			dev_info(dev, "Bad device tree value\n");
			ret = -EINVAL;
			goto error;
		}
	}
	
	dev_info(dev, "i am out of the device tree\n");

	/* reset period to 10 */
	priv->period = 10;

	dev_info(dev, "the led period is %d\n", priv->period);

	platform_set_drvdata(pdev, priv);

	dev_info(dev, "my_probe() function is exited.\n");

	return 0;

error:
	/* Unregister everything in case of errors */
	for (i = 0; i < priv->num_leds; i++) {
		device_destroy(priv->led_class, MKDEV(MAJOR(priv->led_devt), i));
	}
	class_destroy(priv->led_class);
	unregister_chrdev_region(priv->led_devt, priv->num_leds);
	return ret;
}

static int __exit my_remove(struct platform_device *pdev)
{	

	int i;
	struct led_device *led_count;
	struct keyled_priv *priv = platform_get_drvdata(pdev);
	dev_info(&pdev->dev, "my_remove() function is called.\n");

	if (priv->task_flag == 1) {
		kthread_stop(priv->task);
		priv->task_flag = 0;
	}

	if (priv->led_flag == 1) {
		for (i = 0; i < priv->num_leds; i++) {
			led_count = priv->leds[i];
				gpiod_set_value(led_count->ledd, 0);
		}
	}

	for (i = 0; i < priv->num_leds; i++) {
		device_destroy(priv->led_class, MKDEV(MAJOR(priv->led_devt), i));
	}
	class_destroy(priv->led_class);
	unregister_chrdev_region(priv->led_devt, priv->num_leds);
	dev_info(&pdev->dev, "my_remove() function is exited.\n");
	return 0;
	
}

//申明驱动支持的设备列表
static const struct of_device_id my_of_ids[] = {
	{ .compatible = "arrow,ledpwm"},
	{},
};

MODULE_DEVICE_TABLE(of, my_of_ids);

//添加将要注册到平台总线的platform_driver数据结构
static struct platform_driver my_platform_driver = {
	.probe = my_probe,
	.remove = my_remove,
	.driver = {
		.name = "ledpwm",
		.of_match_table = my_of_ids,
		.owner = THIS_MODULE,
	}
};

//在平台总线上注册驱动程序
module_platform_driver(my_platform_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a platform keyled_class driver that decreases \
		   and increases the led flashing period");

12.8.4 测试调试

insmod keyled_imx_class.ko
ls /sys/class/keyled		//查看keyled下的设备
echo on > /sys/class/keyled/blue/set_led 	//打开蓝色灯
echo off > /sys/class/keyled/blue/set_led 	//关闭蓝色灯
echo on > /sys/class/keyled/green/blink_on_led		//打开蓝色灯闪烁
echo off > /sys/class/keyled/green/blink_off_led		//关闭蓝色灯闪烁
echo 100 > /sys/class/keyled/red/set_period      //改变闪烁周期

rmmod keyled_imx_class.ko

12.9 内核中的睡眠

12.9.1 简介

在Linux内核编程中,常常用到用户进程需要睡眠,并在某些特定工作完成时被唤醒。
当用户进程处于睡眠,将被标记处于特定状态,调度器将其从运行队列中删除。
在原子上下文运行中,不允许睡眠。

#include <linux/wait.h>
wait_queue_head_t my_queue;

init_waitqueue_head()
wait_event()   //将使进程处于睡眠状态(task_uninterruptible)
wake_up()      //唤醒睡眠中下进程
wake_up_interruptible()

12.9.2 案例

12.9.2.1 at91-sama5d2_xplained_common.dtsi设备树

/ {
	model = "Atmel SAMA5D2 Xplained";
	compatible = "atmel,sama5d2-xplained", "atmel,sama5d2", "atmel,sama5";

	chosen {
		stdout-path = "serial0:115200n8";
	};
			pinctrl@fc038000 {
				pinctrl_key_gpio_default: key_gpio_default {
					pinmux = <PIN_PB9__GPIO>;
					bias-pull-up;
				};
			};
			
	int_key{
		compatible = "arrow,intkey";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_key_gpio_default>;
		gpios = <&pioA 41 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&pioA>;
		interrupts = <41  IRQ_TYPE_EDGE_FALLING>;
	};

	//本次使用睡眠设备
	int_key_wait {
		compatible = "arrow,intkeywait";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_key_gpio_default>;
		gpios = <&pioA 41 GPIO_ACTIVE_LOW>;
		interrupt-parent = <&pioA>;
		interrupts = <41  IRQ_TYPE_EDGE_BOTH>;
	};

12.9.2.2 int_sam_key_wait.c睡眠设备代码

//包含的头文件
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/wait.h> /* include wait queue */

#define MAX_KEY_STATES 256

static char *HELLO_KEYS_NAME = "PB_USER";
static char hello_keys_buf[MAX_KEY_STATES];
static int buf_rd, buf_wr;

//创建一个私有数据结构,存储按钮设备特定信息,创建miscdevice字符设备
//wq_data_available参数将在probe时候被动态初始化,初始化等待队列头
//irq获取中断号,生成中断将调用中断处理程序(hello_key_isr)
struct key_priv {
	struct device *dev;
	struct gpio_desc *gpio;
	struct miscdevice int_miscdevice;
	wait_queue_head_t	wq_data_available;
	int irq;		
};

//中断处理函数
//按下或放开一个按钮时,都会产生并处理中断
//通过gpiod_get_value获取GPIO数值,确定是否放下或者放开按钮
//读取数值后,使用wake_up_interruptible函数唤醒进程
static irqreturn_t hello_keys_isr(int irq, void *data)
{
	int val;
	struct key_priv *priv = data;
	dev_info(priv->dev, "interrupt received. key: %s\n", HELLO_KEYS_NAME);

	val = gpiod_get_value(priv->gpio);
	dev_info(priv->dev, "Button state: 0x%08X\n", val);

	if (val == 1)
		hello_keys_buf[buf_wr++] = 'P'; 
	else
		hello_keys_buf[buf_wr++] = 'R';

	if (buf_wr >= MAX_KEY_STATES)
		buf_wr = 0;

	/* Wake up the process */
	wake_up_interruptible(&priv->wq_data_available);

	return IRQ_HANDLED;
}

//内核函数my_dev_read(),当用户态程序对该字符设备文件进行读操作时候,该函数就会被执行
//container_of获取该私有数据结构
static int my_dev_read(struct file *file, char __user *buff,
	               size_t count, loff_t *off)
{
	int ret_val;
	char ch[2];
	struct key_priv *priv;

	priv = container_of(file->private_data,
			    struct key_priv, int_miscdevice);

	dev_info(priv->dev, "mydev_read_file entered\n");

	/* 
	 * Sleep the process 
	 * The condition is checked each time the waitqueue is woken up
         */
	ret_val = wait_event_interruptible(priv->wq_data_available, buf_wr != buf_rd);
	if(ret_val)	
		return ret_val;
	
	/* Send values to user application*/
	ch[0] = hello_keys_buf[buf_rd];
	ch[1] = '\n';
	if(copy_to_user(buff, &ch, 2)){
		return -EFAULT;
	}

	buf_rd++;
	if(buf_rd >= MAX_KEY_STATES)
		buf_rd = 0;
	*off+=1;
	return 2;
}

static const struct file_operations my_dev_fops = {
	.owner = THIS_MODULE,
	.read = my_dev_read,
};

static int __init my_probe(struct platform_device *pdev)
{	
	int ret_val;
	struct key_priv *priv;
	struct device *dev = &pdev->dev;

	dev_info(dev, "my_probe() function is called.\n");

	/* Allocate new structure representing device */
	priv = devm_kzalloc(dev, sizeof(struct key_priv), GFP_KERNEL);
	priv->dev = dev;

	platform_set_drvdata(pdev, priv);

	/* Init the wait queue head */
	init_waitqueue_head(&priv->wq_data_available);

	/* Get Linux IRQ number from device tree using 2 methods */
	priv->gpio = devm_gpiod_get(dev, NULL, GPIOD_IN);
	if (IS_ERR(priv->gpio)) {
		dev_err(dev, "gpio get failed\n");
		return PTR_ERR(priv->gpio);
	}
	priv->irq = gpiod_to_irq(priv->gpio);
	if (priv->irq < 0)
		return priv->irq;
	dev_info(dev, "The IRQ number is: %d\n", priv->irq);
	
	priv->irq = platform_get_irq(pdev, 0);
	if (priv->irq < 0){
		dev_err(dev, "irq is not available\n");
		return priv->irq;
	}
	dev_info(dev, "IRQ_using_platform_get_irq: %d\n", priv->irq);
	
	//申请分配中断线
	ret_val = devm_request_irq(dev, priv->irq, hello_keys_isr,
				   IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
				   HELLO_KEYS_NAME, priv);
	if (ret_val) {
		dev_err(dev, "Failed to request interrupt %d, error %d\n", priv->irq, ret_val);
		return ret_val;
	}

	priv->int_miscdevice.name = "mydev";
	priv->int_miscdevice.minor = MISC_DYNAMIC_MINOR;
	priv->int_miscdevice.fops = &my_dev_fops;

	ret_val = misc_register(&priv->int_miscdevice);
	if (ret_val != 0)
	{
		dev_err(dev, "could not register the misc device mydev\n");
		return ret_val;
	}

	dev_info(dev, "my_probe() function is exited.\n");

	return 0;
}

static int __exit my_remove(struct platform_device *pdev)
{	
	struct key_priv *priv = platform_get_drvdata(pdev);
	dev_info(&pdev->dev, "my_remove() function is called.\n");
	misc_deregister(&priv->int_miscdevice);
	dev_info(&pdev->dev, "my_remove() function is exited.\n");
	return 0;
}

//申明驱动程序支持的设备列表
static const struct of_device_id my_of_ids[] = {
	{ .compatible = "arrow,intkeywait"},
	{},
};

MODULE_DEVICE_TABLE(of, my_of_ids);

//添加到平台总线的platform_driver数据结构
static struct platform_driver my_platform_driver = {
	.probe = my_probe,
	.remove = my_remove,
	.driver = {
		.name = "intkeywait",
		.of_match_table = my_of_ids,
		.owner = THIS_MODULE,
	}
};

module_platform_driver(my_platform_driver);

MODULE_LICENSE("GPL");
MODULE_AUTHOR(" ");
MODULE_DESCRIPTION("This is a platform driver that sends to user space \
		   the number of times we press the switch using INTs");

12.9.2.3 测试调试

insmod int_imx_key_wait.ko
cat /proc/interrupts
cat /dev/my_dev > states //sleep the process
cat states //查看按下按钮次数
rmmod int_imx_key_wait.ko

12.10 内核线程

内核线程的出现是处于在进程上下文中运行内核代码的需要。
本质上,内核线程是工作在内核模式运行并且没有用户地址空间和其他用户属性的线程。

#include <linux/kthread.h>
structure task_struct *kthread_create()      //创建线程内核函数

创建和运行内核线程:

#include <linux/sched.h>

wake_up_process()		//启动内核线程
kthread_run()		//运行内核线程
kthread_stop()		//停止内核线程,通过信号发送来工作

感谢阅读,祝君成功!
-by aiziyou

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jack.Jia

感谢打赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值