5.3 legacy中断控制器驱动

文章介绍了如何在Linux内核中编写一个虚拟的legacy中断控制器驱动,包括驱动代码的编写,如定义irq_chip和irq_domain操作函数,以及设备树的配置。驱动解析设备树,分配并初始化irq_desc,模拟子中断。同时,展示了中断测试设备的驱动如何注册中断处理函数。文章还提及了中断触发和处理的流程,以及上机测试的步骤。
摘要由CSDN通过智能技术生成

legacy类型的中断控制器是一个一对多的中断控制器,他在注册之前需要事先在系统中分配相应的irq_desc

驱动代码编写

实验程序是虚拟的中断控制器驱动,它利用 GIC_SPI 210 号中断为父中断,利用一个随机数生成函数来模拟子中断控制器产生的中断。

设备树编写

设备树包括两部分,分别是虚拟中断控制器的设备树和中断控制器测试设备的设备树

//在顶层设备树根节点中加入如下节点
	//虚拟中断控制器的设备树
	virtual_intc: virtual_interrupt_controller {
		compatible = "atk,virtual_intc";
		//此节点是一个中断控制器,使用是需要传递两个参数
		interrupt-controller;
		#interrupt-cells = <2>;
		//父中断控制器是 intc
		interrupt-parent = <&intc>;
		//GIC_SPI 210对应GIC中断控制器中的编号为242,因为GIC0~31是SGI中断和PPI中断
		//通过命令"devmem 0xa002121c 32 0x40000"向0xa002121c写0x40000即可触发此中断
		interrupts = <GIC_SPI 210 IRQ_TYPE_LEVEL_HIGH>;
	};
	//中断测试设备的设备树
	virtual_intc_keys {
		compatible = "atk,virtual_intc_key";
		//父中断控制器是virtual_intc
		interrupt-parent = <&virtual_intc>;
		//使用父中断控制中的中断
		interrupts = <0 IRQ_TYPE_LEVEL_HIGH>,
					 <1 IRQ_TYPE_LEVEL_HIGH>,
					 <2 IRQ_TYPE_LEVEL_HIGH>,
					 <3 IRQ_TYPE_LEVEL_HIGH>;
	};
//用make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabihf- dtbs -j8编译设备树
//用新的.dtb文件启动系统

驱动代码编写

legacy中断控制器的核心便是实现一个irq_domain对象,主要包括以下步骤:

  1. 实现irq_domain的设备树解析函数、map函数、unmap函数,设备树解析函数用于解析设备树,map函数用于设置irq_desc,unmap函数函数执行与map函数的操作
  2. 实现中断控制器操作函数
  3. 预先在内核中分配irq_desc
  4. 分配并初始化一个irq_domain

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/clk.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/random.h>
#include <linux/irq.h>
#include <linux/irqdomain.h>
#include <linux/irqchip/chained_irq.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <linux/slab.h>
#include <linux/gpio/driver.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/random.h>
#include <linux/of_irq.h>

//虚拟中断控制器的中断域
static struct irq_domain *virtual_intc_domain;

/* 中断控制器操作函数 */
//响应中断
static void virtual_intc_irq_ack(struct irq_data *data)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);
}

//屏蔽指定中断
static void virtual_intc_irq_mask(struct irq_data *data)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);
}

//屏蔽并响应中断
static void virtual_intc_irq_mask_ack(struct irq_data *data)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);
}

//开启中断
static void virtual_intc_irq_unmask(struct irq_data *data)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);
}

//中断处理完成调用
static void virtual_intc_irq_eoi(struct irq_data *data)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);
}

//中断控制器操作函数
static struct irq_chip virtual_intc_irq_chip = {
	.name = "atk_virtual_intc",
	.irq_ack = virtual_intc_irq_ack,
	.irq_mask  = virtual_intc_irq_mask,
	.irq_mask_ack = virtual_intc_irq_mask_ack,
	.irq_unmask= virtual_intc_irq_unmask,
	.irq_eoi = virtual_intc_irq_eoi,
};

/* 中断域操作函数 */
//解析设备树
static int virtual_intc_irq_xlate(struct irq_domain *d, struct device_node *node,
							const u32 *intspec, unsigned int intsize,
							unsigned long *out_hwirq, unsigned int *out_type)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);

	if(intsize != 2)
	{
		printk("The device tree format is incorrect\n");
		return -EINVAL;
	}

	//设备树参数的第一个是参数物理中断号
	*out_hwirq = intspec[0];
	//设备树参数的第二个是参数中断触发方式
	*out_type = intspec[1] & IRQ_TYPE_SENSE_MASK;

	return 0;
}

//解析设备树
static int virtual_intc_irq_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
								unsigned long *out_hwirq, unsigned int *out_type)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);

	//解析设备树
	if(fwspec->param_count != 2)
	{
		printk("The device tree format is incorrect\n");
		return -EINVAL;
	}

	//设备树参数的第一个是参数物理中断号
	*out_hwirq = fwspec->param[0];
	//设备树参数的第二个是参数中断触发方式
	*out_type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;

	return 0;
}

//设置irq_desc
static int virtual_intc_irq_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hwirq)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);

	//绑定irq_desc的中断处理函数和irq_chip,这里绑定默认中断处理函数handle_level_irq,它会遍历irq_desc中注册的中断事务
	irq_domain_set_info(d, virq, hwirq, 
						&virtual_intc_irq_chip,
						NULL, handle_level_irq,
						NULL, NULL);

	return 0;
}

//执行与 map 相反的操作
static void virtual_intc_irq_unmap(struct irq_domain *d, unsigned int virq)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);
}

//设置 irq_desc
static int virtual_intc_irq_alloc(struct irq_domain *d, unsigned int virq,
							unsigned int nr_irqs, void *arg)
{
	int i;
	unsigned long hwirq;
	unsigned int type;
	struct irq_fwspec *fwspec = arg;

	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);

	//解析设备树
	if(fwspec->param_count != 2)
	{
		printk("The device tree format is incorrect\n");
		return -EINVAL;
	}
	//设备树参数的第一个是参数物理中断号
	hwirq = fwspec->param[0];
	//设备树参数的第二个是参数中断触发方式
	type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;

	//绑定irq_desc的中断处理函数和irq_chip,这里绑定默认中断处理函数handle_level_irq,它会遍历irq_desc中注册的中断事务
	for(i=0; i<nr_irqs; i++)
	{
		irq_domain_set_info(d, virq+i, hwirq+i, 
							&virtual_intc_irq_chip,
							NULL, handle_level_irq,
							NULL, NULL);
	}

	return 0;
}

//执行与 alloc 相反的操作
static void virtual_intc_irq_free(struct irq_domain *d, unsigned int virq, unsigned int nr_irqs)
{
	printk("function:%s line:%d\n", __FUNCTION__, __LINE__);

	irq_domain_free_irqs_common(d, virq, nr_irqs);
}

static const struct irq_domain_ops virtual_intc_domain_ops = {
	.xlate = virtual_intc_irq_xlate,
	.translate = virtual_intc_irq_translate,
	.map = virtual_intc_irq_map,
	.unmap = virtual_intc_irq_unmap,
	.alloc = virtual_intc_irq_alloc,
	.free = virtual_intc_irq_free,
};

//随机产生硬件中断,利用随机数函数模拟
static int virtual_intc_get_hwirq(void)
{
	return get_random_int() & 0x3;
}

//父中断的中断处理函数
/* 它的功能是分辨是哪一个hwirq发生中断,并通过generic_handle_irq函数去调用对应的irq_desc[].handle_irq */
static void virtual_intc_irq_handler(struct irq_desc *desc)
{
	int hwirq;
	
	struct irq_chip *chip = irq_desc_get_chip(desc);

	//进入中断处理函数
	chained_irq_enter(chip, desc);

	/* 获取硬件中断号 */
	hwirq = virtual_intc_get_hwirq();

	/* 调用通用的中断处理函数,依次执行action中的中断事务函数 */
	generic_handle_irq(irq_find_mapping(virtual_intc_domain, hwirq));

	//退出中断处理函数
	chained_irq_exit(chip, desc);
}

//设备和驱动匹配成功执行
static int virtual_intc_probe(struct platform_device *pdev)
{
	int irq_parent;
	int irq_base;

	//获取父中断号
	irq_parent = of_irq_get(pdev->dev.of_node, 0);
	if(irq_parent <= 0)
	{
		printk("get parent failed");
		return irq_parent;
	}
	//重新设置父中断的中断处理函数
	irq_set_chained_handler_and_data(irq_parent, virtual_intc_irq_handler, NULL);

	/* 预先在内核中分配irq_desc */
	irq_base = irq_alloc_descs(-1, 0, 4, numa_node_id());
	if (irq_base < 0)
	{
		printk("Couldn't allocate IRQ numbers\n");
		return irq_base;
	}

	/* 分配并初始化一个中断域 */
	virtual_intc_domain = irq_domain_add_legacy(pdev->dev.of_node, 4, 
												irq_base, 0, 
												&virtual_intc_domain_ops, NULL);
	if (!virtual_intc_domain)
	{
		printk("couldn't allocate irq domain (DT).\n");
		return -ENODEV;
	}

	return 0;
}

//设备或驱动卸载时执行
static int virtual_intc_remove(struct platform_device *pdev)
{
	irq_domain_remove(virtual_intc_domain);
	return 0;
}

/* 匹配列表,用于设备树和平台驱动匹配 */
static const struct of_device_id virtual_intc_of_match[] = {
	{.compatible = "atk,virtual_intc"},
	{ /* Sentinel */ }
};
//平台驱动
static struct platform_driver virtual_intc_drv = {
	.driver = {
		.name = "virtual_intc",
		.owner = THIS_MODULE,
		.pm = NULL,
		.of_match_table = virtual_intc_of_match,
	},
	.probe = virtual_intc_probe,
	.remove = virtual_intc_remove,
};

static int __init virtual_intc_drv_init(void)
{
	int result;

	printk("%s\r\n", __FUNCTION__);

	//注册平台驱动
	result = platform_driver_register(&virtual_intc_drv);
	if(result < 0)
	{
		printk("add cdev faikey\r\n");
		return result;
	}

	return 0;
}

static void __exit virtual_intc_drv_exit(void)
{
	printk("%s\r\n", __FUNCTION__);

	//注销平台驱动
	platform_driver_unregister(&virtual_intc_drv);
}

module_init(virtual_intc_drv_init);
module_exit(virtual_intc_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("virtual_intc_drivrer");

中断测试程序

中断测试程序非常简单,就是从设备树获取中断号,然后通过request_irq注册对应的中断事件处理函数即可

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/input.h>

struct virtual_intc_key_handle {
	int irq;
	char name[32];
};

static irqreturn_t virtual_intc_key_handler(int irq, void *dev)
{
	struct virtual_intc_key_handle *handle;
	
	handle = dev;
	printk("function:%s line%d name:%s\n", __FUNCTION__, __LINE__, handle->name);

	//返回IRQ_HANDLED,表示中断被成功处理
	return IRQ_HANDLED;
}

//设备和驱动匹配成功执行
static int virtual_intc_key_probe(struct platform_device *pdev)
{
	int result;
	int index;
	struct virtual_intc_key_handle *handle;

	printk("%s\r\n", __FUNCTION__);

	for(index=0; ; index++)
	{
		//分配设备句柄,devm表示模块卸载时自动释放
		handle = devm_kzalloc(&pdev->dev, sizeof(struct virtual_intc_key_handle), GFP_KERNEL);
		if(!handle)
		{
			printk("alloc memory faikey\r\n");
			return -ENOMEM;
		}

		//获取中断号
		handle->irq = of_irq_get(pdev->dev.of_node, index);
		if(handle->irq <= 0)
			break;

		//注册中断,devm表示模块卸载时自动释放
		snprintf(handle->name, sizeof(handle->name), "virtual_intc_key%d", index);
		result = devm_request_irq(&pdev->dev, handle->irq, virtual_intc_key_handler, 0, handle->name, handle);
		if(result != 0)
		{
			printk("request irq failed\r\n");
			return result;
		}
	}

	return 0;
}

//设备或驱动卸载时执行
static int virtual_intc_key_remove(struct platform_device *pdev)
{
	printk("%s\r\n", __FUNCTION__);

	return 0;
}

/* 匹配列表,用于设备树和平台驱动匹配 */
static const struct of_device_id virtual_intc_key_of_match[] = {
	{.compatible = "atk,virtual_intc_key"},
	{ /* Sentinel */ }
};
//平台驱动
static struct platform_driver virtual_intc_key_drv = {
	.driver = {
		.name = "virtual_intc_key",
		.owner = THIS_MODULE,
		.pm = NULL,
		.of_match_table = virtual_intc_key_of_match,
	},
	.probe = virtual_intc_key_probe,
	.remove = virtual_intc_key_remove,
};
static int __init virtual_intc_key_drv_init(void)
{
	int result;

	printk("%s\r\n", __FUNCTION__);

	//注册平台驱动
	result = platform_driver_register(&virtual_intc_key_drv);
	if(result < 0)
	{
		printk("add cdev faikey\r\n");
		return result;
	}

	return 0;
}

static void __exit virtual_intc_key_drv_exit(void)
{
	printk("%s\r\n", __FUNCTION__);

	//注销平台驱动
	platform_driver_unregister(&virtual_intc_key_drv);
}

module_init(virtual_intc_key_drv_init);
module_exit(virtual_intc_key_drv_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("csdn");
MODULE_DESCRIPTION("virtual_intc_key_dev");

上机测试

  1. 修改设备树,然后用新的设备树启动目标板,设备树中的GIC_SPI 中断号根据芯片手册进行修改
  2. 这里下载驱动代码,然后进行编译,并拷贝到目标板root目录
  3. 执行insmod virtual_intc.ko加载虚拟中断控制器驱动,此时会看到map函数被调用
    在这里插入图片描述
  4. 执行命令加载中断控制器测试设备的驱动insmod virtual_intc_key.ko,此时可以看到设备树解析函数translate和中断使能函数unmask被调用
    在这里插入图片描述
  5. 执行命令devmem 0xa002121c 32 0x40000,触发GIC_SPI 210中断(通过向特定寄存器写入值来实现)可以看到mask_ack函数、unmask函数以及测试驱动程序的中断函数被调用
    在这里插入图片描述
    注意:在卸载时必须先卸载virtual_intc_key.ko,再卸载virtual_intc.ko,否则可能会报段错误
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值