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对象,主要包括以下步骤:
- 实现irq_domain的设备树解析函数、map函数、unmap函数,设备树解析函数用于解析设备树,map函数用于设置irq_desc,unmap函数函数执行与map函数的操作
- 实现中断控制器操作函数
- 预先在内核中分配irq_desc
- 分配并初始化一个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");
上机测试
- 修改设备树,然后用新的设备树启动目标板,设备树中的GIC_SPI 中断号根据芯片手册进行修改
- 从这里下载驱动代码,然后进行编译,并拷贝到目标板root目录
- 执行insmod virtual_intc.ko加载虚拟中断控制器驱动,此时会看到map函数被调用
- 执行命令加载中断控制器测试设备的驱动insmod virtual_intc_key.ko,此时可以看到设备树解析函数translate和中断使能函数unmask被调用
- 执行命令devmem 0xa002121c 32 0x40000,触发GIC_SPI 210中断(通过向特定寄存器写入值来实现)可以看到mask_ack函数、unmask函数以及测试驱动程序的中断函数被调用
注意:在卸载时必须先卸载virtual_intc_key.ko,再卸载virtual_intc.ko,否则可能会报段错误