Linux内核中IRQ Domain的结构、操作及映射机制详解

news2024/11/26 8:28:00

往期内容

本专栏往期内容,interrtupr子系统:

  1. 深入解析Linux内核中断管理:从IRQ描述符到irq domain的设计与实现

pinctrl和gpio子系统专栏:

  1. 专栏地址:pinctrl和gpio子系统

  2. 编写虚拟的GPIO控制器的驱动程序:和pinctrl的交互使用

    – 末片,有专栏内容观看顺序

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有专栏内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有专栏内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有专栏内容观看顺序

img

1.数据结构描述

1.1 irq_domain

在内核中,irq domain的概念由struct irq_domain表示:
struct irq_domain {
    struct list_head link;
    const char *name;
    const struct irq_domain_ops *ops; ----callback函数
    void *host_data;

    /* Optional data */
    struct device_node *of_node; ----该interrupt domain对应的interrupt controller的device node
    struct irq_domain_chip_generic *gc; ---generic irq chip的概念,本文暂不描述

    /* reverse map data. The linear map gets appended to the irq_domain */
    irq_hw_number_t hwirq_max; ----该domain中最大的那个HW interrupt ID
    unsigned int revmap_direct_max_irq; ----
    unsigned int revmap_size; ---线性映射的size,for Radix Tree map和no map,该值等于0
    struct radix_tree_root revmap_tree; ----Radix Tree map使用到的radix tree root node
    unsigned int linear_revmap[]; -----线性映射使用的lookup table
};

linux内核中,所有的irq domain被挂入一个全局链表,链表头定义如下:

static LIST_HEAD(irq_domain_list);

struct irq_domain中的link成员就是挂入这个队列的节点。通过irq_domain_list这个指针,可以获取整个系统中HW interrupt ID和IRQ number的mapping DB。host_data定义了底层interrupt controller使用的私有数据,和具体的interrupt controller相关(对于GIC,该指针指向一个struct gic_chip_data数据结构)。

对于线性映射:

(1)linear_revmap保存了一个线性的lookup table,index是HW interrupt ID,table中保存了IRQ number值

(2)revmap_size等于线性的lookup table的size。

(3)hwirq_max保存了最大的HW interrupt ID

(4)revmap_direct_max_irq没有用,设定为0。revmap_tree没有用。

对于Radix Tree map:

(1)linear_revmap没有用,revmap_size等于0。

(2)hwirq_max没有用,设定为一个最大值。

(3)revmap_direct_max_irq没有用,设定为0。

(4)revmap_tree指向Radix tree的root node。

1.2 irq_domain_ops

irq_domain结构体中的成员,也就是callback函数

struct irq_domain_ops {
    int (*match)(struct irq_domain *d, struct device_node *node);
    int (*map)(struct irq_domain *d, unsigned int virq, irq_hw_number_t hw);
    void (*unmap)(struct irq_domain *d, unsigned int virq);
    int (*xlate)(struct irq_domain *d, struct device_node *node,
             const u32 *intspec, unsigned int intsize,
             unsigned long *out_hwirq, unsigned int *out_type);
#ifdef	CONFIG_IRQ_DOMAIN_HIERARCHY
  	/* extended V2 interfaces to support hierarchy irq_domains */
  	int (*alloc)(struct irq_domain *d, unsigned int virq,
  		     unsigned int nr_irqs, void *arg);
  	void (*free)(struct irq_domain *d, unsigned int virq,
  		     unsigned int nr_irqs);
  	void (*activate)(struct irq_domain *d, struct irq_data *irq_data);
  	void (*deactivate)(struct irq_domain *d, struct irq_data *irq_data);
  	int (*translate)(struct irq_domain *d, struct irq_fwspec *fwspec,
  			 unsigned long *out_hwirq, unsigned int *out_type);
#endif
};

}

xlate:大概就是将设备树中节点的hwirq and type解析出来。就是将指定的设备(node参数)上若干个(intsize参数)中断属性(intspec参数)翻译成HW interrupt ID(out_hwirq参数)和trigger类型(out_type)。

match:判断一个指定的interrupt controller(node参数)是否和一个irq domain匹配(d参数),如果匹配的话,返回。内核中很少定义这个callback函数,实际上struct irq_domain中有一个of_node指向了对应的interrupt controller的device node,因此,如果不提供该函数,那么default的匹配函数其实就是判断irq domain的of_node成员是否等于传入的node参数。

map:调用map函数的时机是在创建(或者更新)HW interrupt ID(hw参数)和IRQ number(virq参数)关系的时候。貌似还有的根据hirq找到对应的virq,应该是在irq_set_chip_and_handler调用时提供的handle函数中

从发生一个中断到调用该中断的handler仅仅调用一个request_threaded_irq是不够的,还需要针对该irq number设定:

(1)设定该IRQ number对应的中断描述符(struct irq_desc)的irq chip

(2)设定该IRQ number对应的中断描述符的highlevel irq-events handler

(3)设定该IRQ number对应的中断描述符的 irq chip data

就是根据传入的virq,设置对应的irq_desc中的irq_chip、handle函数、irq_data:irq_set_chip_data()、irq_set_chip_and_handler()

alloc:如果是gic_irq_domain_alloc函数的话

不仅负责分配虚拟 IRQ 号并建立硬件中断号(hwirq)和虚拟 IRQ 号(virq)之间的映射,还会为分配的 IRQ 号设置相应的 irq_descirq_data 结构体。这包括配置中断处理程序 (handle_irq)、中断类型、触发模式等与中断相关的详细信息。

当调用 gic_irq_domain_alloc 时,它通常会执行以下任务:

  1. 分配 IRQ 描述符 (**irq_desc**):

    • 为特定的虚拟 IRQ 号分配一个 irq_desc 结构体。这是中断子系统用来管理特定中断的信息块,包含状态、处理程序等。
  2. 设置中断处理程序 (**handle_irq**):

    • irq_desc 结构体中的 handle_irq 字段是一个函数指针,用于指定当中断发生时应该调用哪个处理函数。在 GIC 的情况下,这可能是一个标准的 GIC 处理程序,例如 handle_fasteoi_irq
  3. 配置 **irq_data** 结构:

    • irq_datairq_desc 的一部分,包含与中断相关的硬件信息。gic_irq_domain_alloc 会填充这个结构,包括:

      • hwirq(硬件中断号)。
      • 中断控制器的地址、数据和特定于硬件的配置。
  4. 设置中断触发类型和属性:

    • 可能会设置中断的触发模式(边沿触发或电平触发)以及其他属性,如中断优先级和屏蔽状态。
  5. 建立映射关系:

    • irq_descirq_data 与特定的 hwirqvirq 关联起来,这样当中断发生时,内核可以正确地调用相应的处理程序。

translate: 用于将硬件中断号(hwirq)从设备树或其他硬件描述符中翻译成域内部的表示。

2.irq_domain的用法

中断控制器驱动程序通过以下方式创建并注册一个irq_domain。

irq_domain_add_*()

irq_domain_create_*()

每个映射方法都有不 同的分配器函数(比如irq_domain_add_linear内部调用的是irq_domain_add_)

函数成功后会返回一个指向irq_domain的指针。 调用者必须向分配器函数提供一个irq_domain_ops结构体。

在大多数情况下,irq_domain在开始时是空的,没有任何hwirq和IRQ号之间的映射。 通过调用irq_create_mapping()将映射添加到irq_domain中,该函数接受 irq_domain和一个hwirq号作为参数。 如果hwirq的映射还不存在,那么它将分配 一个新的Linux irq_desc,将其与hwirq关联起来,并调用.map()回调,这样驱动 程序就可以执行任何必要的硬件设置。

一旦建立了映射,可以通过多种方法检索或使用它:

  • irq_resolve_mapping()返回一个指向给定域和hwirq号的irq_desc结构指针, 如果没有映射则返回NULL。
  • irq_find_mapping()返回给定域和hwirq的Linux IRQ号,如果没有映射则返回0。
  • irq_linear_revmap()现与irq_find_mapping()相同,已被废弃。
  • generic_handle_domain_irq()处理一个由域和hwirq号描述的中断。

请注意,irq域的查找必须发生在与RCU读临界区兼容的上下文中。

在调用irq_find_mapping()之前,至少要调用一次irq_create_mapping()函数, 以免描述符不能被分配。

如果驱动程序有Linux的IRQ号或irq_data指针,并且需要知道相关的hwirq号(比 如在irq_chip回调中),那么可以直接从irq_data->hwirq中获得。

3.irq_domain映射

系统中HW interrupt ID和IRQ number的mapping DB是在整个系统初始化的过程中建立起来的,过程如下:

(1)DTS文件描述了系统中的interrupt controller以及外设IRQ的拓扑结构,在linux kernel启动的时候,由bootloader传递给kernel(实际传递的是DTB)。

(2)在Device Tree初始化的时候,形成了系统内所有的device node的树状结构,当然其中包括所有和中断拓扑相关的数据结构(所有的interrupt controller的node和使用中断的外设node)

(3)在machine driver初始化的时候会调用of_irq_init函数,在该函数中会扫描所有interrupt controller的节点,并调用适合的interrupt controller driver进行初始化。毫无疑问,初始化需要注意顺序,首先初始化root,然后first level,second level,最好是leaf node。在初始化的过程中,一般会调用上节中的接口函数向系统增加irq domain。有些interrupt controller会在其driver初始化的过程中创建映射

(4)在各个driver初始化的过程中,创建映射

当一个hwirq被映射 时,会给hwirq分配一个irq_desc,并将irq号存储在表中:hwirq和irq之间的映射

img

3.1 注册映射

3.1.1 线性映射

其实就是一个lookup table,HW interrupt ID作为index,通过查表可以获取对应的IRQ number。对于Linear map而言,interrupt controller对其HW interrupt ID进行编码的时候要满足一定的条件:hw ID不能过大,而且ID排列最好是紧密的。

static inline struct irq_domain *irq_domain_add_linear(struct device_node *of_node,
                     unsigned int size,---------该interrupt domain支持多少IRQ
                     const struct irq_domain_ops *ops,---callback函数
                     void *host_data)-----driver私有数据
{
    return __irq_domain_add(of_node, size, size, 0, ops, host_data);
}

------------------------------------------------------------------
irq_domain_create_linear()

线性反向映射维护了一个固定大小的表,该表以hwirq号为索引。 当一个hwirq被映射 时,会给hwirq分配一个irq_desc,并将irq号存储在表中。

当最大的hwirq号固定且数量相对较少时,线性图是一个很好的选择(~<256)。 这种 映射的优点是固定时间查找IRQ号,而且irq_descs只分配给在用的IRQ。 缺点是该表 必须尽可能大的hwirq号。

irq_domain_add_linear()在功能上是等价的, 除了第一个参数不同–前者接受一个Open Firmware特定的 ‘struct device_node’ 而 后者接受一个更通用的抽象 ‘struct fwnode_handle’ 。

大多数驱动应该使用线性映射

3.1.2 树状映射

建立一个Radix Tree来维护HW interrupt ID到IRQ number映射关系。HW interrupt ID作为lookup key,在Radix Tree检索到IRQ number。如果的确不能满足线性映射的条件,可以考虑Radix Tree map。实际上,内核中使用Radix Tree map的只有powerPC和MIPS的硬件平台。

static inline struct irq_domain *irq_domain_add_tree(struct device_node *of_node,
                     const struct irq_domain_ops *ops,
                     void *host_data)
{
    return __irq_domain_add(of_node, 0, ~0, 0, ops, host_data);
}

------------------------------------------------------------------
irq_domain_create_tree()

irq_domain维护着从hwirq号到Linux IRQ的radix的树状映射。 当一个hwirq被映射时, 一个irq_desc被分配,hwirq被用作radix树的查找键。

如果hwirq号可以非常大,树状映射是一个很好的选择,因为它不需要分配一个和最大hwirq 号一样大的表。 缺点是,hwirq到IRQ号的查找取决于表中有多少条目。

irq_domain_add_tree()和irq_domain_create_tree()在功能上是等价的,除了第一 个参数不同——前者接受一个Open Firmware特定的 ‘struct device_node’ ,而后者接受 一个更通用的抽象 ‘struct fwnode_handle’ 。

很少有驱动应该需要这个映射。

3.1.3 无映射

些中断控制器很强,可以通过寄存器配置HW interrupt ID而不是由物理连接决定的。例如PowerPC 系统使用的MPIC (Multi-Processor Interrupt Controller)。在这种情况下,不需要进行映射,我们直接把IRQ number写入HW interrupt ID配置寄存器就OK了,这时候,生成的HW interrupt ID就是IRQ number,也就不需要进行mapping了。

irq_domain_add_nomap()

当硬件中的hwirq号是可编程的时候,就可以采用无映射类型。 在这种情况下,最好将 Linux IRQ号编入硬件本身,这样就不需要映射了。 调用irq_create_direct_mapping() 会分配一个Linux IRQ号,并调用.map()回调,这样驱动就可以将Linux IRQ号编入硬件中。

大多数驱动程序无法使用此映射,现在它由CONFIG_IRQ_DOMAIN_NOMAP选项控制。 这类接口的逻辑很简单,根据自己的映射类型,初始化struct irq_domain中的各个成员,调用__irq_domain_add将该irq domain挂入irq_domain_list的全局列表。

3.1.4 传统映射类型

irq_domain_add_simple()
irq_domain_add_legacy()
irq_domain_create_simple()
irq_domain_create_legacy()

传统映射是已经为 hwirqs 分配了一系列 irq_descs 的驱动程序的特殊情况。 当驱动程 序不能立即转换为使用线性映射时,就会使用它。 例如,许多嵌入式系统板卡支持文件使用 一组用于IRQ号的定义(#define),这些定义被传递给struct设备注册。 在这种情况下, 不能动态分配Linux IRQ号,应该使用传统映射。

顾名思义,_legacy()系列函数已被废弃,只是为了方便对古老平台的支持而存在。 不应该增加新的用户。当_simple()系列函数的使用导致遗留行为时,他们也是如此。

传统映射假设已经为控制器分配了一个连续的IRQ号范围,并且可以通过向hwirq号添加一 个固定的偏移来计算IRQ号,反之亦然。 缺点是需要中断控制器管理IRQ分配,并且需要为每 个hwirq分配一个irq_desc,即使它没有被使用。

只有在必须支持固定的IRQ映射时,才应使用传统映射。 例如,ISA控制器将使用传统映射来 映射Linux IRQ 0-15,这样现有的ISA驱动程序就能得到正确的IRQ号。

大多数使用传统映射的用户应该使用irq_domain_add_simple()或 irq_domain_create_simple(),只有在系统提供IRQ范围时才会使用传统域,否则将使用 线性域映射。这个调用的语义是这样的:如果指定了一个IRQ范围,那么 描述符将被即时分配 给它,如果没有范围被分配,它将不会执行 irq_domain_add_linear() 或 irq_domain_create_linear(),这意味着 no irq 描述符将被分配。

一个简单域的典型用例是,irqchip供应商同时支持动态和静态IRQ分配。

为了避免最终出现使用线性域而没有描述符被分配的情况,确保使用简单域的驱动程序在任何 irq_find_mapping()之前调用irq_create_mapping()是非常重要的,因为后者实际上 将用于静态IRQ分配情况。

irq_domain_add_simple()和irq_domain_create_simple()以及 irq_domain_add_legacy()和irq_domain_create_legacy()在功能上是等价的,只 是第一个参数不同–前者接受Open Firmware特定的 ‘struct device_node’ ,而后者 接受一个更通用的抽象 ‘struct fwnode_handle’ 。

struct irq_domain *irq_domain_add_legacy(struct device_node *of_node,
                     unsigned int size,
                     unsigned int first_irq,
                     irq_hw_number_t first_hwirq,
                     const struct irq_domain_ops *ops,
                     void *host_data)
{
    struct irq_domain *domain;

    domain = __irq_domain_add(of_node, first_hwirq + size,----注册irq domain
                  first_hwirq + size, 0, ops, host_data);
    if (!domain)
        return NULL;

    irq_domain_associate_many(domain, first_irq, first_hwirq, size); ---创建映射

    return domain;
}

这时候,对于这个版本的GIC driver而言,初始化之后,HW interrupt ID和IRQ number的映射关系已经建立,保存在线性lookup table中,size等于GIC支持的中断数目,具体如下:

index 0~15对应的IRQ无效

16号IRQ  <------------------>16号HW interrupt ID

17号IRQ  <------------------>17号HW interrupt ID

……

3.1.5 IRQ域层级结构

在某些架构上,可能有多个中断控制器参与将一个中断从设备传送到目标CPU。 x86平台上典型的中断传递路径:

Device --> IOAPIC -> Interrupt remapping Controller -> Local APIC -> CPU

涉及到的中断控制器有三个:

  1. IOAPIC 控制器
  2. 中断重映射控制器
  3. Local APIC 控制器

为了支持这样的硬件拓扑结构,使软件架构与硬件架构相匹配,为每个中断控制器建立一 个irq_domain数据结构,并将这些irq_domain组织成层次结构。

在建立irq_domain层次结构时,靠近设备的irq_domain为子域,靠近CPU的 irq_domain为父域。所以在上面的例子中,将建立如下的层次结构。

CPU Vector irq_domain (root irq_domain to manage CPU vectors)
        ^
        |
Interrupt Remapping irq_domain (manage irq_remapping entries)
        ^
        |
IOAPIC irq_domain (manage IOAPIC delivery entries/pins)

使用irq_domain层次结构的主要接口有四个:

  1. irq_domain_alloc_irqs(): 分配IRQ描述符和与中断控制器相关的资源来传递这些中断。
  2. irq_domain_free_irqs(): 释放IRQ描述符和与这些中断相关的中断控制器资源。
  3. irq_domain_activate_irq(): 激活中断控制器硬件以传递中断。
  4. irq_domain_deactivate_irq(): 停用中断控制器硬件,停止传递中断。

为了支持irq_domain层次结构,需要做如下修改:

  1. 一个新的字段 ‘parent’ 被添加到irq_domain结构中;它用于维护irq_domain的层次信息。
  2. 一个新的字段 ‘parent_data’ 被添加到irq_data结构中;它用于建立层次结构irq_data以 匹配irq_domain层次结构。irq_data用于存储irq_domain指针和硬件irq号。
  3. 新的回调被添加到irq_domain_ops结构中,以支持层次结构的irq_domain操作。

在支持分层irq_domain和分层irq_data准备就绪后,为每个中断控制器建立一个irq_domain结 构,并为每个与IRQ相关联的irq_domain分配一个irq_data结构。现在我们可以再进一步支持堆 栈式(层次结构)的irq_chip。也就是说,一个irq_chip与层次结构中的每个irq_data相关联。 一个子irq_chip可以自己或通过与它的父irq_chip合作来实现一个所需的操作。

通过堆栈式的irq_chip,中断控制器驱动只需要处理自己管理的硬件,在需要的时候可以向其父 irq_chip请求服务。所以我们可以实现更简洁的软件架构。

为了让中断控制器驱动程序支持irq_domain层次结构,它需要做到以下几点:

  1. 实现 irq_domain_ops.alloc 和 irq_domain_ops.free
  2. 可选择地实现 irq_domain_ops.activate 和 irq_domain_ops.deactivate.
  3. 可选择地实现一个irq_chip来管理中断控制器硬件。
  4. 不需要实现irq_domain_ops.map和irq_domain_ops.unmap,它们在层次结构 irq_domain中是不用的。

irq_domain层次结构绝不是x86特有的,大量用于支持其他架构,如ARM、ARM64等。

3.2 创建映射

上面是注册了一个irq_domain,具体HW interrupt ID和IRQ number的映射关系都是空的,因此,具体各个irq domain如何管理映射所需要的database还是需要建立的。例如:对于线性映射的irq domain,我们需要建立线性映射的lookup table对于Radix Tree map,我们要把那个反应IRQ number和HW interrupt ID的Radix tree建立起来。创建映射有四个接口函数:

(1)调用irq_create_mapping函数建立HW interrupt ID和IRQ number的映射关系。该接口函数以irq domain和HW interrupt ID为参数,返回IRQ number(这个IRQ number是动态分配的)。该函数的原型定义如下:

extern unsigned int irq_create_mapping(struct irq_domain *host,
                       irq_hw_number_t hwirq);
驱动调用该函数的时候必须提供HW interrupt ID,也就是意味着driver知道自己使用的
HW interrupt ID,而一般情况下,HW interrupt ID其实对具体的driver应该是不可见的,
不过有些场景比较特殊,例如GPIO类型的中断,它的HW interrupt ID和GPIO有着特定的关系,
driver知道自己使用那个GPIO,也就是知道使用哪一个HW interrupt ID了。

(2)irq_create_strict_mappings。这个接口函数用来为一组HW interrupt ID建立映射。具体函数的原型定义如下:

extern int irq_create_strict_mappings(struct irq_domain *domain,
                      unsigned int irq_base,
                      irq_hw_number_t hwirq_base, int count);                   

(3)irq_create_of_mapping。看到函数名字中的of(open firmware),这个接口当然是利用device tree进行映射关系的建立。具体函数的原型定义如下:

extern unsigned int irq_create_of_mapping(struct of_phandle_args *irq_data);

通常,一个普通设备的device tree node已经描述了足够的中断信息,在这种情况下,
该设备的驱动在初始化的时候可以调用irq_of_parse_and_map这个接口函数进行该device node中
和中断相关的内容(interrupts和interrupt-parent属性)进行分析,并建立映射关系,
具体代码如下:
unsigned int irq_of_parse_and_map(struct device_node *dev, int index)
{
    struct of_phandle_args oirq;

    if (of_irq_parse_one(dev, index, &oirq))----分析device node中的interrupt相关属性
        return 0;

    return irq_create_of_mapping(&oirq);-----创建映射,并返回对应的IRQ number
}
对于一个使用Device tree的普通驱动程序(推荐这样做),基本上初始化需要调用irq_of_parse_and_map获取IRQ number,
然后调用request_threaded_irq申请中断handler。

(4)irq_create_direct_mapping。这是给no map那种类型的interrupt controller使用的

3.3 中断号映射库

目前Linux内核的设计使用了一个巨大的数字空间,每个独立的IRQ源都被分配了一个不 同的数字。 当只有一个中断控制器时,这很简单,但在有多个中断控制器的系统中,内核必须确保每 个中断控制器都能得到非重复的Linux IRQ号(数字)分配。

注册为唯一的irqchips的中断控制器编号呈现出上升的趋势:例如GPIO控制器等不同 种类的子驱动程序通过将其中断处理程序建模为irqchips,即实际上是级联中断控制器, 避免了重新实现与IRQ核心系统相同的回调机制。

在这里,中断号与硬件中断号离散了所有种类的对应关系:而在过去,IRQ号可以选择, 使它们与硬件IRQ线进入根中断控制器(即实际向CPU发射中断线的组件)相匹配,现 在这个编号仅仅是一个数字。

出于这个原因,我们需要一种机制将控制器本地中断号(即硬件irq编号)与Linux IRQ 号分开。

irq_alloc_desc*() 和 irq_free_desc*() API 提供了对irq号的分配,但它们不 提供任何对控制器本地IRQ(hwirq)号到Linux IRQ号空间的反向映射的支持。

irq_domain 库在 irq_alloc_desc*() API 的基础上增加了 hwirq 和 IRQ 号码 之间的映射。 相比于中断控制器驱动开放编码自己的反向映射方案,更喜欢用 irq_domain来管理映射。

irq_domain还实现了从抽象的irq_fwspec结构体到hwirq号的转换(到目前为止是 Device Tree和ACPI GSI),并且可以很容易地扩展以支持其它IRQ拓扑数据源。

这也使得用户在调用irq_alloc_desc后,hwirq和irq之间已经有了映射,再通过调用3.1中注册irq_domain的接口(比如irq_domain_add_legacy)获得已经有映射关系的irq_domain,不需要再调用irq_create_mapping函数建立HW interrupt ID和IRQ number的映射关系。

irq_alloc_desc*() 系列函数:
这些函数用于分配一系列的 IRQ 描述符,并返回一个起始的 IRQ 号码(irq_base)。当你使用这些 API 时,可以选择是否指定 hwirq 和 IRQ 号码之间的映射。
这些函数可以直接通过参数来设定映射关系,例如在分配描述符时提供的 hwirq 和 irq_base 值。
在调用这些 API 时,内核会自动处理并维护 hwirq 和 IRQ 号码之间的映射关系,这在某种程度上相当于调用了 irq_create_mapping()。

irq_create_mapping() 函数:
该函数是更直接的用于在现有的 irq_domain 中创建或查询硬件中断号(hwirq)和虚拟 IRQ 号之间的映射。
它将指定的 hwirq 映射到 irq_domain 中的一个对应的 IRQ 号码,并返回这个 IRQ 号码。
如果映射已经存在,它会直接返回现有的映射关系中的 IRQ 号码。
共同点和区别:
共同点:

两者都可以建立硬件中断号(hwirq)与虚拟 IRQ 号(virq)之间的映射,并在系统中维护这一关系。
区别:
irq_alloc_desc*() 不仅仅是用于映射,还涉及到分配 IRQ 描述符的工作。它为新分配的 IRQ 提供了内存空间并初始化描述符。
irq_create_mapping() 更专注于映射本身,通常在描述符已经分配好的情况下使用它来建立或查找 hwirq 和 virq 之间的映射。

3.5 其它情况

static struct irq_chip virtual_intc_irq_chip = {
	.name			= "100ask_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_map(struct irq_domain *h, unsigned int virq,
			  irq_hw_number_t hw)
{
	/* 1. 给virq提供处理函数
	 * 2. 提供irq_chip用来mask/unmask中断
	 */
	
	irq_set_chip_data(virq, h->host_data);
	//irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_edge_irq); /* handle_edge_irq就是handleC */
	irq_set_chip_and_handler(virq, &virtual_intc_irq_chip, handle_level_irq); /* handle_level_irq就是handleC */
	//irq_set_nested_thread(virq, 1);
	//irq_set_noprobe(virq);

	return 0;
}


static const struct irq_domain_ops virtual_intc_domain_ops = {
	.xlate = irq_domain_xlate_onetwocell,
	.map   = virtual_intc_irq_map,
};

static int virtual_intc_probe(struct platform_device *pdev)
{	
	struct device_node *np = pdev->dev.of_node;
	int irq_to_parent;
	//int irq_base;
	
	/* 1. virutal intc 会向GIC发出n号中断 */
	/* 1.1 从设备树里获得virq_n */
	irq_to_parent = platform_get_irq(pdev, 0);
	printk("virtual_intc_probe irq_to_parent = %d\n", irq_to_parent);
	
	/* 1.2 设置它的irq_desc[].handle_irq, 它的功能时分辨是哪一个hwirq, 调用对应的irq_desc[].handle_irq */
	irq_set_chained_handler_and_data(irq_to_parent, virtual_intc_irq_handler, NULL);

	
	/* 2. 分配/设置/注册一个irq_domain */
	//irq_base = irq_alloc_descs(-1, 0, 4, numa_node_id());
	//printk("virtual_intc_probe irq_base = %d\n", irq_base);

	/* Usage:
	 *  a. dts: 定义使用哪个hwirq
	 *  b. 内核解析设备树时分配irq_desc,得到virq
	 *  c. (hwirq, virq) ==>存入domain
	 */

	virtual_intc_domain = irq_domain_add_linear(np, 4, 
					     &virtual_intc_domain_ops, NULL);
	
	return 0;
}

在很多情况下,使用 irq_domain_add_linear 创建 IRQ 域后,确实需要调用 irq_create_mapping 或者 irq_domain_map 来建立硬件中断号(hwirq)和虚拟中断号(virq)之间的映射。

然而,在某些情况下,尤其是像你代码中描述的情形,如果设备树(DTS)已经定义了中断资源,并且系统在解析设备树的时候自动分配了中断描述符(irq_desc),则可能不需要显式调用 irq_create_mapping。具体原因如下:

\1. 设备树自动映射:

  • 当设备树中已经定义了硬件中断号(hwirq)时,设备树解析器(OF)可能会自动为这些 hwirq 分配相应的虚拟中断号(virq),并将其存储在 irq_domain 中。
  • 这种情况下,映射可能是在设备树解析过程中由框架自动完成的,因此不需要手动调用 irq_create_mapping(上面代码应该就是处于这种情况,或者也有可能是irq_alloc_descs调用时已经完成了irq和hwirq之间的映射)

\2. 特定中断控制器驱动的逻辑:

  • 某些中断控制器驱动在调用 irq_domain_add_linear 时,会直接处理硬件中断号(hwirq)的映射,可能在 irq_domain_ops 回调函数中自动处理 hwirq 和 virq 的关联。
  • 使用的 irq_domain_ops(如 virtual_intc_domain_ops)可能已经内置了自动映射的逻辑,因此省去了显式调用 irq_create_mapping 的步骤。

\3. 使用平台的现有机制:

  • 如果基于某个平台或者已有的中断控制器框架,那么它们可能已经实现了自动分配和映射的机制。只要你按规定创建 irq_domain,框架可能会处理其余的细节。

虽然大多数情况下需要调用 irq_create_mapping 来显式建立 hwirq 和 virq 的映射,但在某些特定环境下,尤其是当设备树和平台驱动框架已经处理了这些映射时,你可以不需要显式调用这个函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2236357.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 继承:代码传承的魔法棒,开启奇幻编程之旅

文章目录 一.继承的概念及定义1.1继承的概念1.2继承类1.2.1继承方法 1.3继承模板 二.基类和派生类的转换三.继承中的作用域四.派生类的默认成员函数4.1默认成员函数的行为4.2实现一个无法被继承的类 五.继承与友元六.继承与静态成员七.多继承和菱形继承7.1多继承和菱形继承7.2虚…

无人车之编队控制算法篇

一、编队控制算法概述 无人车编队控制算法旨在实现多辆无人车之间的协同行驶&#xff0c;保持预定的队形和间距&#xff0c;以应对各种复杂环境和任务需求。该算法通常包括队形生成、队形保持、队形变换和编队模式切换等关键步骤。 二、编队控制算法的核心要素 队形生成&…

【大数据学习 | kafka高级部分】kafka的数据同步和数据均衡

1. 数据同步 通过上图我们发现每个分区的数据都不一样&#xff0c;但是三个分区对外的数据却是一致的 这个时候如果第二个副本宕机了 但是如果是leader副本宕机了会发生什么呢&#xff1f; 2. 数据均衡 在线上程序运行的时候&#xff0c;有的时候因为上面副本的损坏&#xff…

计算机网络——TCP篇

TCP篇 基本认知 TCP和UDP的区别? TCP 和 UDP 可以使用同一个端口吗&#xff1f; 可以的 传输层中 TCP 和 UDP在内核中是两个完全独立的软件模块。可以根据协议字段来选择不同的模块来处理。 TCP 连接建立 TCP 三次握手过程是怎样的&#xff1f; 一次握手:客户端发送带有 …

Xserver v1.4.2发布,支持自动重载 nginx 配置

Xserver——优雅、强大的 php 集成开发环境 本次更新为大家带来了更好的用户体验。 &#x1f389; 下载依赖组件时&#xff0c;显示进度条&#xff0c;展示下载进度。 &#x1f389; 保存站点信息和手动修改 vhost 配置文件之后&#xff0c;自动重载 nginx 配置 &#x1f41e…

Day107:代码审计-PHP模型开发篇MVC层RCE执行文件对比法1day分析0day验证

知识点&#xff1a; 1、PHP审计-MVC开发-RCE&代码执行 2、PHP审计-MVC开发-RCE&命令执行 3、PHP审计-MVC开发-RCE&文件对比 MVC 架构 MVC流程&#xff1a; Controller截获用户发出的请求&#xff1b;Controller调用Model完成状态的读写操作&#xff1b;Contr…

飞书API-获取tenant_access_token

1.在飞书工作台创建应用&#xff0c;跳到开发者后台&#xff0c;选创建企业自建应用 2.设置并发布应用 必须要发布应用才可以开始使用了&#xff01;&#xff01;&#xff01; 3.调用获取token的API 参考链接&#xff1a; 开发文档 - 飞书开放平台https://open.feishu.cn/do…

推荐 4 个 YYDS 的开源项目!

如下是本期盘点的几个好玩有趣的开源项目&#xff0c;目录&#xff1a; 1. 网页截屏转为代码 2. 将文档转为 Markdown 和 JSon 格式 3. 帮你写代码的 AI 助手 4. 开源 RAG 工具 01 网页截屏转为代码 screenshot-to-code 利用先进的大模型识别屏幕截图中的 UI 元素、布局以及其他…

Android关机流程知多少?

在 Android 中&#xff0c;关机流程涉及系统各个组件的协同工作&#xff0c;确保设备在断电之前能够安全地关闭所有活动并保存数据。以下是 Android 系统中关机流程的详细介绍&#xff1a; 1. 用户触发关机请求 关机流程由用户的操作触发&#xff0c;通常有以下几种方式&#…

Mac保护电池健康,延长电池使用寿命的好方法

使用Mac的过程中&#xff0c;如何延长电池的使用寿命是大家非常关心的问题&#xff0c;而养成一个良好的充电习惯能够有效的延长电池的使用寿命 避免过度充电和过度放电能够有效的保护电池&#xff0c;因此长时间的充电与长时间放点都不可取&#xff0c;但是在日常的使用过程中…

Android中Activity启动的模式

在 Android 开发中&#xff0c;Activity 的启动模式&#xff08;Launch Mode&#xff09;定义了当启动一个 Activity 时&#xff0c;系统会如何处理它的实例。不同的启动模式可以影响 Activity 在任务栈中的管理方式&#xff0c;对用户的使用体验产生直接影响。下面详细介绍四种…

基础算法练习--滑动窗口(已完结)

算法介绍 滑动窗口算法来自tcp协议的一种特性,它的高效使得其也变成了算法题的一种重要考点.滑动窗口的实现实际上也是通过两个指针前后遍历集合实现,但是因为它有固定的解题格式,我将其单独做成一个篇章. 滑动窗口的解题格式: 首先,定义两个指针left和right,与双指针不同的…

基于SpringBoot的Java教学支持系统开发指南

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理教学辅助平台的相关信息成为必然。开发合适…

C++builder中的人工智能(11):双曲正切激活函数(ANN函数)?

在这篇文章中&#xff0c;我们将探讨双曲正切函数&#xff08;tanh&#xff09;是什么&#xff0c;以及如何在C中使用这个函数。让我们来回答这些问题。 在AI中激活函数意味着什么&#xff1f; 激活函数&#xff08;phi()&#xff09;&#xff0c;也称为转移函数或阈值函数&a…

Unity SRP学习笔记(二)

Unity SRP学习笔记&#xff08;二&#xff09; 主要参考&#xff1a; https://catlikecoding.com/unity/tutorials/custom-srp/ https://docs.unity.cn/cn/2022.3/ScriptReference/index.html 中文教程部分参考&#xff08;可选&#xff09;&#xff1a; https://tuncle.blog/c…

帮你快速理解并巧记设计模式

经常因为记不住或不能理解设计模式而苦恼的童鞋们注意了&#xff0c;闲暇之余总结了常用的22中设计模式&#xff0c;并一一举例&#xff0c;帮助大家快速理解、牢记&#xff0c;如有不对的地方&#xff0c;欢迎大家指正哈 创建型模式 单例模式&#xff08;Singleton Pattern&…

STM32CUBEIDE FreeRTOS操作教程(八):queues多队列

STM32CUBEIDE FreeRTOS操作教程&#xff08;八&#xff09;&#xff1a;queues多队列 STM32CUBE开发环境集成了STM32 HAL库进行FreeRTOS配置和开发的组件&#xff0c;不需要用户自己进行FreeRTOS的移植。这里介绍最简化的用户操作类应用教程。以STM32F401RCT6开发板为例&#…

防火墙|WAF|漏洞|网络安全

防火墙|WAF|漏洞|网络安全 防火墙 根据内容分析数据包&#xff1a; 1、源IP和目的IP地址 2、有效负载中的内容。 3、数据包协议&#xff08;例如&#xff0c;连接是否使用 TCP/IP 协议&#xff09;。 4、应用协议&#xff08;HTTP、Telnet、FTP、DNS、SSH 等&#xff09;。 5…

【04】【Maven项目热部署】将Maven项目热部署到远程tomcat服务器上

1.虽然现在Maven中央仓库中支持的tomcat插件只支持到tomcat7这个版本&#xff0c;但是可以利用这个插件对Web项目进行热部署&#xff0c;热部署到远程服务器的tomcat服务器上&#xff0c;远程服务器上的tomcat版本可以是更高的版本&#xff0c;比如说tomcat8、9、10或更高的版本…

大数据新视界 -- 大数据大厂之 Impala 性能优化:融合机器学习的未来之路(上 (2-1))(11/30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…