Linux 中断控制器驱动程序浅析

前言

  1. 因为之前师傅让我编写了一个 I2C 扩展 GPIO 控制器,但是这个 GPIO 控制器并没有要求编写中断的内容,所以就一直搁浅了。后面突发奇想,感觉还是要了解一下中断控制器的驱动程序如何编写,因此就有了该篇博客。
  2. 在韦东山老师的驱动大全中,其实已经介绍了中断控制器的驱动程序如何编写。但是我后面简单了解了一下,发现韦东山老师的中断驱动都是参考的 IMX6ULL 的中断控制器驱动。而在一些特殊的项目中,可能会要求外扩中断控制器,例如 ste-nomadik-nhk15 开发板就采用了两个 I2C 外扩的中断控制器。
i2c0 {
	stmpe0: stmpe2401@43 {
		compatible = "st,stmpe2401";
		reg = <0x43>;
		// ...
		stmpe_gpio43: stmpe_gpio {
			compatible = "st,stmpe-gpio";
			gpio-controller;
			#gpio-cells = <2>;
			interrupt-controller;
			// ...
		};
	};
	stmpe1: stmpe2401@44 {
		compatible = "st,stmpe2401";
		reg = <0x44>;
		// ...
		stmpe_gpio44: stmpe_gpio {
			compatible = "st,stmpe-gpio";
			gpio-controller;
			#gpio-cells = <2>;
			interrupt-controller;
			//...
		};
	};
	// ...
};
  1. ARM Cortex-A和Cortex-M中断处理浅析 一文中,我简单介绍了 Cortex-A 和Cortex-M 中断处理机制。我们知道了对于 Cortex-A 核的中断处理,需要软件实现。
  2. Linux 做了一套中断处理框架,需要驱动工程师按照这套框架进行适配。这里,我将会简单介绍一下,如果你希望向 Linux 中注册一个 中断控制器,应该如何做。
  3. 个人邮箱:zhangyixu02@gmail.com
  4. 微信公众号:风正豪

在这里插入图片描述

中断控制器驱动程序

重要结构体

  1. 所谓的 Linux 驱动程序,本质上就是按照 Linux 内核开发者规定的框架进行填鸭。这里我将介绍比较重要的结构体,作为驱动开发者,如果要编写一个中断控制器要填充的结构体成员有哪些。
  2. irq_chip 结构体重要成员:
  • irq_chip.irq_ack : 当中断产生时,将会调用该成员函数清除中断源信号,标记该中断被处理,为新的相同中断触发做准备。无特殊需求,可以直接调用 Linux 提供的 irq_gc_ack_set_bit 函数。
  • irq_chip.irq_mask : 中断产生,需要调用该成员函数屏蔽相同的中断源,防止同一个中断执行过程中再次被自己中断导致重入问题。无特殊需求,可以直接调用 Linux 提供的 irq_gc_mask_set_bit 函数。
  • irq_chip.irq_mask_ack : 是 irq_chip.irq_ackirq_chip.irq_mask 之和。如果定义了这个成员,那么 irq_chip.irq_ackirq_chip.irq_mask 就没有必要再进行定义。
  • irq_chip.irq_unmask : 当中断执行完成之后,我们需要调用该函数解除对该中断的中断源屏蔽解除。无特殊需求,可以直接调用 Linux 提供的 irq_gc_mask_clr_bit 函数。
  • irq_chip.irq_set_type : 当注册中断时,设置中断的触发类型时将会调用该函数。这个必须提供,并且必须由芯片原厂提供,因为不同的中断控制器其中断触发类型不同。
  1. 需要支持 irq_gc_mask_clr_bitirq_gc_mask_set_bitirq_gc_ack_set_bit 函数就需要填充 irq_chip_type.regs 中的 ackmask 成员。
/**
 * struct irq_chip_regs - 中断控制器寄存器偏移量结构体
 * @enable:        使能寄存器相对于 reg_base 的偏移量
 * @disable:        禁用寄存器相对于 reg_base 的偏移量
 * @mask:        屏蔽寄存器相对于 reg_base 的偏移量
 * @ack:        确认寄存器相对于 reg_base 的偏移量
 * @eoi:        结束中断寄存器相对于 reg_base 的偏移量
 * @type:        类型配置寄存器相对于 reg_base 的偏移量
 * @polarity:        极性配置寄存器相对于 reg_base 的偏移量
 */
struct irq_chip_regs {
        unsigned long                enable;    // 使能寄存器的偏移量
        unsigned long                disable;   // 禁用寄存器的偏移量
        unsigned long                mask;      // 屏蔽寄存器的偏移量
        unsigned long                ack;       // 确认寄存器的偏移量
        unsigned long                eoi;       // 结束中断寄存器的偏移量
        unsigned long                type;      // 类型配置寄存器的偏移量
        unsigned long                polarity;  // 极性配置寄存器的偏移量
};

成员变量被调用时机

  1. 设备驱动调用 request_irq 将会导致 irq_chip.irq_unmask 被调用用以使能中断。
  2. 如果是电平触发的函数,在函数执行之前,会调用 irq_chip.irq_maskirq_chip.irq_ack 来屏蔽中断,执行完成后调用 irq_chip.irq_unmask 重新使能中断。
if (chip->irq_mask_ack) {
        chip->irq_mask_ack(&desc->irq_data);
} else {
        chip->irq_mask(&desc->irq_data);
        if (chip->irq_ack)
                chip->irq_ack(&desc->irq_data);
}
  1. 屏蔽中断可以发送在外设、中断控制器、CPU 三个位置。
  • 设备驱动层调用 local_irq_disablelocal_irq_enable 针对的不是外部的中断控制器,而是直接让 CPU 自身不响应中断请求。
  • 设备驱动层调用 disable_irqenable_irq 针对的是中断控制器。当调用 disable_irq ,那么将会暂时屏蔽中断,在内核中的实现层面是做了延后屏蔽,直到 enable_irq 再执行 ISR。
  • 外设端的中断信号产生高度依赖外设本身,因此 Linux 并不会提供标准的 API,而是由外设驱动自行设计。

GIC 中断控制器

  1. GIC 是中断管理控制器,是直接与 CPU 核连接的。该控制器驱动程序是由 ARM 官方提供,如果是作为芯片原厂的工程师,是无需关心的。对于绝大多数人也接触不到,因此我不做介绍。(笑,水字,狗头)
  2. 如果你还是希望学习阅读 GIC 控制器如何编写,请自行阅读 Linux-4.9.88\drivers\irqchip\irq-gic.c

GPIO 级联中断控制器

  1. 对于 GPIO 级联中断控制器就是挂载在 GPIO 控制器下的中断控制器,例如前言中所说的 I2C GPIO 外扩中断控制器就属于级联在 GPIO 控制器下的中断控制器。
  2. 这类中断控制器可以由芯片原厂工程师编写,也可以由方案商的驱动工程师根据项目需求进行编写。对于想要学习这类中断控制器驱动编写的,可以参考 Linux-4.9.88\drivers\gpio\gpio-stmpe.cLinux-4.9.88\drivers\gpio\gpio-pca953x.c

设备树

i2c0 {
	stmpe0: stmpe2401@43 {
		compatible = "st,stmpe2401";
		reg = <0x43>;
		// ...
		stmpe_gpio43: stmpe_gpio {
			compatible = "st,stmpe-gpio";
			gpio-controller;
			#gpio-cells = <2>;
			interrupt-controller;
			// ...
		};
	};
	stmpe1: stmpe2401@44 {
		compatible = "st,stmpe2401";
		reg = <0x44>;
		// ...
		stmpe_gpio44: stmpe_gpio {
			compatible = "st,stmpe-gpio";
			gpio-controller;
			#gpio-cells = <2>;
			interrupt-controller;
			//...
		};
	};
	// ...
};

驱动程序

  1. 如下为一个简单的 GPIO 级联中断控制器的框架,请自行参考上述两个文件对照这个框架学习。
// 如下内容可以在设备树总设置,为了方便理解,这里用宏定义代替
#define chip0               0
#define chip1               1
#define extend_irq_chip_id  chip0


// 定义一个结构体 extend_irq_chip,表示片外中断控制器的中断芯片信息
static struct irq_chip extend_irq_chip = {
    .name            = "extend_irq_chip",    // 中断芯片的名称
    .irq_mask        = extend_irq_mask,      // 中断屏蔽函数,为了防止中断函数真正执行,此时相同中断触发导致出现问题
    .irq_unmask      = extend_irq_unmask,    // 中断解除屏蔽函数,函数执行完成,解除屏蔽
    .irq_bus_lock    = extend_irq_bus_lock,  // 中断总线锁定函数,用于在访问中断总线时加锁,因为是片外控制器,需要防止多中断重入问题
    .irq_bus_sync_unlock = extend_irq_bus_sync_unlock, // 中断总线同步解锁函数,用于在访问完中断总线后解锁
    .irq_set_type    = extend_irq_set_type,  // 设置中断类型的函数,例如边缘触发或电平触发
    .irq_shutdown    = extend_irq_shutdown,  // 中断关闭函数,用于在设备关闭时处理相关中断
};

// 中断处理函数
static irqreturn_t extend_irq_handler(int irq, void *devid)
{
    if (devid == chip0) {
        // 执行 chip0 相关操作
    } else if (devid == chip1) {
        // 执行 chip1 相关操作
    } else {
        return IRQ_NONE;
    }
    return IRQ_HANDLED;
}

static int extend_irq_probe(struct i2c_client *client,
                   const struct i2c_device_id *i2c_id)
{
    struct gpio_chip *gc;
    /* 1. 对相关参数进行初始化
     * 因为需要级联到 GPIO 控制器中,所以这里需要通过设备树设置当前芯片的父级芯片 
     */
    gc->parent = &client->dev;
    
    /* 2. 注册中断处理函数。
     * 片外中断控制器肯定是挂载在一个 GPIO 引脚上。因此我们需要注册这个引脚的中断
     * CPU 与片外中断控制器通讯属于慢速通讯,所以中断顶半部就不注册函数,
     * 所有函数处理推移至中断线程化的下半部。
     * 因为该驱动程序可能支持多个芯片,所以需要传入 extend_irq_chip_id 用于标识不同的芯片
     */
    devm_request_threaded_irq(&client->dev,
                                client->irq,
                                NULL,
                                extend_irq_handler,
                                IRQF_TRIGGER_LOW | IRQF_ONESHOT |IRQF_SHARED,
                                dev_name(&client->dev), 
                                extend_irq_chip_id);
    
    /* 3. 我们需要将片外的中断控制器级联到 GPIO 控制器,因此设置它的 IRQ 域在 GPIO 控制器之下
     * 该函数最终执行效果本质上就是给片外中断控制器设置好 irq_domain 域,这个域在 GPIO 控制器下
     * acpi_gpio_deferred_req_irqs_list 是一个全局可见的链表,也就是挂载在 GPIO 控制器下的 irq_domain 域
     */
    /*
        static LIST_HEAD(acpi_gpio_deferred_req_irqs_list);
        gpiochip_irqchip_add_nested
         -> gpiochip_irqchip_add_key
          -> acpi_gpiochip_request_interrupts
           -> list_add(&acpi_gpio->deferred_req_irqs_list_entry,
                       &acpi_gpio_deferred_req_irqs_list);
    */
    gpiochip_irqchip_add_nested(gc,
                               &extend_irq_chip,
                               0,
                               handle_simple_irq,
                               IRQ_TYPE_NONE);
    /* 4. 设置级联中断处理函数 */
    /*
        gpiochip_set_nested_irqchip
            -> gpiochip_set_cascaded_irqchip
                -> irq_set_chained_handler_and_data
    */
    gpiochip_set_nested_irqchip(gc,
                                &extend_irq_chip,
                                client->irq);
}

链式中断控制器

  1. 这个名词是韦东山老师发明的,所以我这里也直接用他所创作的这个名词了。
  2. 下图为链式后中断控制器和层级中断控制器的定义,对于链式中断就是说,中断控制器只会给 GIC 控制器发送一个中断信号,但是链式中断控制器里面还有很多细分的中断信号需要判断。上面的 GPIO 级联中断控制器也就是链式中断控制器的一种,只不过这里的链式中断控制器是直接连接的 GIC,而 GPIO 级联中断控制器是连接的 GPIO 中断控制器。
  3. 层级式中断控制器就比较方便理解,级联中断控制器会像 GIC 中断控制器发送多个中断信号,每个中断信号对应一个中断事件,就不再需要进行细分。

在这里插入图片描述

设备树

/{
   virtual_intc: virtual_intc_100ask {
        compatible = "100ask,virtual_intc";
        reg = <0x0209c000 0x4000>;
        
        interrupt-controller;
        #interrupt-cells = <2>;

        interrupt-parent = <&intc>;
        interrupts = <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>;

    };

    gpio_keys_100ask {
        compatible = "100ask,gpio_key";
        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>;
    };
    
};

驱动程序

  1. 实话实说,感觉韦东山老师的链式中断控制器的示例代码有一点点小小的问题。也可能是我理解上存在偏差,这里是我参考 imx6ull 和 rk3568 的 GPIO 中断控制器写的一个简单的示例代码。各位可参考 Linux-4.9.88\drivers\gpio\gpio-mxc.c 配合如下示例代码学习。
static struct irq_domain *gpio_irq_domain;

struct private_data {
    // ...
};

/* 中断函数 */
static void gpio_irq_handler(struct irq_desc *desc)
{
    u32 irq_stat;
    // 因为驱动会需要匹配多设备,通过私有数据进行适配
    struct private_data *port = irq_desc_get_handler_data(desc);
    struct irq_chip *chip = irq_desc_get_chip(desc);

    // 该函数会调用 irq_desc[].irq_data.chip.irq_mask 或 irq_mask_ack 来屏蔽中断
    chained_irq_enter(chip, desc);
    // 读取中断控制器的寄存器,知道具体的发生了哪一个中断
    gpio_hwirq = get_gpio_hwirq();
    // 调用 irq_desc[].handle_irq。使用根据硬件中断号和中断域找到具体的中断函数
    generic_handle_irq(irq_find_mapping(port->domain, gpio_hwirq));
    // 该函数会调用 irq_desc[].irq_data.chip.irq_unmask 来重新使能中断
    chained_irq_exit(chip, desc);
}

static int mxc_gpio_probe(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;
    struct resource *iores;      // 寄存器物理地址信息
    void __iomem *virbase;       // 中断寄存器物理地址映射后的虚拟地址
    int gic_hwirq,irq_base;      // GIC 中断号;中断描述符
    struct irq_chip_generic *gc; // 芯片的通用信息描述结构体
    
    /* 1. 从设备树中获取相关资源 */
    /* 1.1 从设备树 interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>; 获取 GIC 中断号
     * 指示当前中断控制器共享外设中断,要用 55 号中断
     */
    gic_hwirq = platform_get_irq(pdev, 0);
    /* 1.2 从设备树 reg = <0x0209c000 0x4000>; 获取寄存器物理地址信息,并将其进行映射 */
    iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    virbase = devm_ioremap_resource(&pdev->dev, iores);
    
    /* 2. 根据获取到的 GIC 中断号,设置它的 irq_desc[].handle_irq
     * 当该中断控制器产生中断信号,CPU 会根据 GIC 中断号跳转到注册的 gpio_irq_handler 函数中
     */
    irq_set_chained_handler_and_data(gic_hwirq,gpio_irq_handler,NULL);
    
    /* 3. 分配注册一个 irq_domain ,有如下三种方法
     * irq_domain_add_legacy
     * irq_domain_add_linear
     * irq_domain_add_hierarchy     
     */
    /* 3.1 方法1:动态分配中断描述符
     * &pdev->dev :指向设备结构体的指针,表示请求中断的设备
     * -1 : 不指定起始中断号,内核将自动选择可用的中断号
     * 0 : 分配的中断号的起始偏移量
     * 4 :分配 4 个连续的中断描述符,该中断控制器支持 4 个中断
     * numa_node_id() : 内存和处理器的访问速度可能会有所不同,因此在分配中断时指定 NUMA 节点可以帮助优化性能
     */
    irq_base = devm_irq_alloc_descs(&pdev->dev, -1, 0, 4, numa_node_id());
    /* 3.1 注册一个 irq_domain(IRQ 域) */
    gpio_irq_domain = irq_domain_add_legacy(np, 4, irq_base, 0,
                         &irq_domain_simple_ops, NULL);
    
    /* 3.2 方法2:注册一个 irq_domain(IRQ 域),这个是只有使用的时候才会分配,更灵活 */
    gpio_irq_domain = irq_domain_add_linear(np, 4,&irq_generic_chip_ops, NULL);
    
    /* 4. 为中断域分配芯片信息资源,并进行初始化
     * rk : irq_alloc_domain_generic_chips + irq_get_domain_generic_chip
     * mxc : devm_irq_alloc_generic_chip
     * 这里传入 handle_level_irq 表示中断是电平触发,所以会调用 irq_mask 用于屏蔽中断
     * 如果传入的是 handle_edge_irq ,那么中断是边沿触发,那么 irq_mask 将不会被调用 
     */
    gc = devm_irq_alloc_generic_chip(&pdev->dev, "gpio-test", 1, irq_base,
                     virbase, handle_level_irq);
    gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
    gc->chip_types[0].chip.irq_mask = irq_gc_mask_clr_bit;
    gc->chip_types[0].chip.irq_unmask = irq_gc_mask_set_bit;
    gc->chip_types[0].chip.irq_set_type = ...; // 芯片原厂实现,当设备驱动层调用注册中断类型时的处理方法
    gc->chip_types[0].regs.ack = ...;  // 确认寄存器相对于 reg_base 的偏移量
    gc->chip_types[0].regs.mask = ...; // 屏蔽寄存器相对于 reg_base 的偏移量
    
    /* 5. 注册中断,本质上调用的都是 __irq_do_set_handler 函数
     * rk : irq_set_chained_handler_and_data
     * mxc : devm_irq_setup_generic_chip
     */
    /*
irq_set_chained_handler_and_data
    -> irq_get_desc_buslock
        -> __irq_do_set_handler
    */
    /*
devm_irq_setup_generic_chip
    -> irq_setup_generic_chip
        -> irq_set_chip_and_handler
            -> irq_set_chip_and_handler_name
                -> irq_set_chip
                -> __irq_set_handler
                    -> __irq_do_set_handler
    */
    devm_irq_setup_generic_chip(&pdev->dev, gc, IRQ_MSK(4),
                     IRQ_GC_INIT_NESTED_LOCK,
                     IRQ_NOREQUEST, 0);
}

层级中断控制器

设备树


#define m 123

/{
   virtual_intc: virtual_intc_100ask {
        compatible = "100ask,virtual_intc";

        interrupt-controller;
        #interrupt-cells = <2>;

        interrupt-parent = <&intc>;
        // GIC 中断号起始位置
		upper_hwirq_base = <122>;  // imx6ull
		//upper_hwirq_base = <210>;  // stm32mp157
    };

    gpio_keys_100ask {
        compatible = "100ask,gpio_key";
        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>;
    };
    
};

驱动程序

  1. 层级式中断控制器就很简单了,因为他们不涉及到函数分发的问题。这里直接给韦东山老师的示例代码了。
  2. 各位可以配合 Linux-4.9.88\arch\arm\mach-imx\gpc.c 学习。
#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>
/* FIXME: for gpio_get_value() replace this with direct register read */
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/of_device.h>
#include <linux/bug.h>


static struct irq_domain *virtual_intc_domain;
static u32 upper_hwirq_base;

static int virtual_intc_domain_translate(struct irq_domain *d,
				    struct irq_fwspec *fwspec,
				    unsigned long *hwirq,
				    unsigned int *type)
{
	// 检查 fwspec 是否是设备树节点
	if (is_of_node(fwspec->fwnode)) {
		/* 如果参数数量不是 2,则返回错误,interrupts = <0 IRQ_TYPE_LEVEL_HIGH>;
		 * 对于 imx6ull 的 gpc 节点来说,#interrupt-cells = <3>;
		 * 因此改在在 gpc 节点下的中断需要有 3 个参数,interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>;
		 */
		if (fwspec->param_count != 2)
			return -EINVAL;

		// 提取硬件中断号 (hwirq) 和中断类型 (type)
		*hwirq = fwspec->param[0];
		*type = fwspec->param[1];
		return 0;
	}

	return -EINVAL;
}

static void virtual_intc_irq_unmask(struct irq_data *d)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	irq_chip_unmask_parent(d);
}

static void virtual_intc_irq_mask(struct irq_data *d)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	irq_chip_mask_parent(d);
}

static void virtual_intc_irq_eoi(struct irq_data *d)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	irq_chip_eoi_parent(d);
}

static struct irq_chip virtual_intc_chip = {
	.name			= "virtual_intc",
	.irq_mask		= virtual_intc_irq_mask,
	.irq_unmask 	= virtual_intc_irq_unmask,
	.irq_eoi        = virtual_intc_irq_eoi,
};


static int virtual_intc_domain_alloc(struct irq_domain *domain,
				  unsigned int irq,
				  unsigned int nr_irqs, void *data)
{
	struct irq_fwspec *fwspec = data;
	struct irq_fwspec parent_fwspec;
	irq_hw_number_t hwirq;
	int i;
	
	/* 设置irq_desc[irq] */

	/* 1. 设置irq_desc[irq].irq_data, 里面含有virtual_intc irq_chip */
	hwirq = fwspec->param[0];
	for (i = 0; i < nr_irqs; i++)
		irq_domain_set_hwirq_and_chip(domain, irq + i, hwirq + i,
					      &virtual_intc_chip, NULL);

	/* 2. 设置irq_desc[irq].handle_irq,  来自GIC */
	parent_fwspec.fwnode = domain->parent->fwnode;
	/* 因为该中断控制器是挂载在 GIC 下,而 GIC 节点 #interrupt-cells = <3>;
	 * 但是韦东山老师编写的虚拟中断控制器设备树示例里面没有 interrupts 属性,因此这里需要手动指定相关值
	 * 如果指定了 interrupts 属性,例如 imx6ul.dtsi 中的 gpc 节点。那么驱动程序就可以写成 parent_fwspec = *fwspec;
	 */
	parent_fwspec.param_count = 3; // 3个参数
	parent_fwspec.param[0]    = 0; // GIC_SPI,共享中断
	parent_fwspec.param[1]    = fwspec->param[0] + upper_hwirq_base; // 加上基地址获取到实际的硬件中断号
	parent_fwspec.param[2]    = fwspec->param[1]; // 中断类型
	
	return irq_domain_alloc_irqs_parent(domain, irq, nr_irqs,
					  &parent_fwspec);
}

static const struct irq_domain_ops virtual_intc_domain_ops = {
	.translate	= virtual_intc_domain_translate, // 解析设备树中的 interrupts = <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>;
	.alloc		= virtual_intc_domain_alloc,
};

static int virtual_intc_probe(struct platform_device *pdev)
{	

	struct irq_domain *parent_domain;
	struct device_node *parent;
	int err;
		

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	// 获取层级中断的硬件中断号基地址
	// upper_hwirq_base = <122>;
	err = of_property_read_u32(pdev->dev.of_node, "upper_hwirq_base", &upper_hwirq_base);

	parent = of_irq_find_parent(pdev->dev.of_node);
	parent_domain = irq_find_host(parent);


	/* 分配/设置/注册irq_domain */
	virtual_intc_domain = irq_domain_add_hierarchy(parent_domain, 0, 4,
					  pdev->dev.of_node, &virtual_intc_domain_ops,
					  NULL);
	
	
	return 0;
}
static int virtual_intc_remove(struct platform_device *pdev)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}


static const struct of_device_id virtual_intc_of_match[] = {
	{ .compatible = "100ask,virtual_intc", },
	{ },
};


static struct platform_driver virtual_intc_driver = {
	.probe		= virtual_intc_probe,
	.remove		= virtual_intc_remove,
	.driver		= {
		.name	= "100ask_virtual_intc",
		.of_match_table = of_match_ptr(virtual_intc_of_match),
	}
};


/* 1. 入口函数 */
static int __init virtual_intc_init(void)
{	
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 1.1 注册一个platform_driver */
	return platform_driver_register(&virtual_intc_driver);
}


/* 2. 出口函数 */
static void __exit virtual_intc_exit(void)
{
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	/* 2.1 反注册platform_driver */
	platform_driver_unregister(&virtual_intc_driver);
}

module_init(virtual_intc_init);
module_exit(virtual_intc_exit);

MODULE_LICENSE("GPL");

调试

  1. 我们可以执行如下命令查看中断控制器注册情况。
$ cd /sys/kernel/irq/[虚拟中断号]
# 查看中断控制器的名称
$ cat chip_name
# 查看硬件中断号
$ cat hwirq
  1. 编写一个设备驱动程序,查看是否能够成功注册中断。
cat /proc/interrupts
  1. 使用 devmem 工具修改 GIC 寄存器的值,手动产生中断信号。

参考

  1. 韦东山驱动大全
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

风正豪

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值