Sunday, July 10, 2022

[SOLVED] How to find my irq number from hwirq number in linux kernel driver module? (finding matching irq_desc)

Issue

I'm doing an linux driver and application test on a qemu arm64 virtual machine. My virtual machine is slightly modified version of arm 'virt' machine and it has our device model included. When I do request_irq for INTID 208 (SPI 176) in linux on my arm64 virtual machine, it returns -EINVAL. I poked into that function and found the desc is returning NULL in function request_threaded_irq.

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
             irq_handler_t thread_fn, unsigned long irqflags,
             const char *devname, void *dev_id)
{
    struct irqaction *action;
    struct irq_desc *desc;
    int retval;
...
desc = irq_to_desc(irq);
    if (!desc) 
        return -EINVAL;
...
}

and irq_to_desc is supposed to return an irq_desc from the irq_radix_tree as shown below.

struct irq_desc *irq_to_desc(unsigned int irq)
{
    return radix_tree_lookup(&irq_desc_tree, irq);
}

void *radix_tree_lookup(const struct radix_tree_root *root, unsigned long index)
{
    return __radix_tree_lookup(root, index, NULL, NULL);
}

void *__radix_tree_lookup(const struct radix_tree_root *root,
              unsigned long index, struct radix_tree_node **nodep,
              void __rcu ***slotp)
{
    struct radix_tree_node *node, *parent;
    unsigned long maxindex;
    void __rcu **slot;

 restart:
    parent = NULL;
    slot = (void __rcu **)&root->xa_head;
    radix_tree_load_root(root, &node, &maxindex);
    if (index > maxindex)
        return NULL;
...
}

and I found my request index is 208 but the maxindex of the radix tree is 63. So it looks like the radix tree of irq_desc has been set to have maximum 64 irq_descs. Where and how can I set this radix tree size? I looked at qemu's hw/arm/ab21q-build-acpi.c for MADT or DSDT table parts but couldn't find what to add. I have my devices address range and irq added in the DSDT and MADT.

static void
build_dsdt(GArray *table_data, BIOSLinker *linker, Ab21qMachineState *vms)
{
...
acpi_dsdt_add_axpu(scope, &memmap[AB21Q_AXPU],
                       (irqmap[AB21Q_AXPU] + ARM_SPI_BASE));
...
}
static void acpi_dsdt_add_axpu(Aml *scope, const MemMapEntry *axpu_memmap,
                                           uint32_t irq)
{
    Aml *dev = aml_device("AXPU");
    aml_append(dev, aml_name_decl("_HID", aml_string("AXPU0011")));
    aml_append(dev, aml_name_decl("_UID", aml_int(0)));

    Aml *crs = aml_resource_template();
    aml_append(crs, aml_memory32_fixed(axpu_memmap->base,
                                       axpu_memmap->size, AML_READ_WRITE));
    aml_append(crs,
               aml_interrupt(AML_CONSUMER, AML_LEVEL, AML_ACTIVE_HIGH,
                             AML_EXCLUSIVE, &irq, 1));
    aml_append(dev, aml_name_decl("_CRS", crs));

    aml_append(scope, dev);
}

I'm not sure if I can just use _HID value as "AXPU0011". If you see anything wrong and have a suggestion, please tell me.

ADD

This is the boot message of the virtual machine related to IRQ.

[    0.000000] NR_IRQS: 64, nr_irqs: 64, preallocated irqs: 0
[    0.000000] GICv3: 224 SPIs implemented
[    0.000000] GICv3: 0 Extended SPIs implemented
[    0.000000] GICv3: Distributor has no Range Selector support
[    0.000000] GICv3: 16 PPIs implemented
[    0.000000] GICv3: no VLPI support, no direct LPI support
[    0.000000] GICv3: CPU0: found redistributor 0 region 0:0x00000000080a0000

Somehow the NR_IRQS is 64. So I tried using SPI 15 which was not assigned. (32 internal IRQs + 15 < 64) and now request_irq returns 0.
So this irq_desc problem was solved(?) for now. (I don’t know why this is so small when CONFIG_SPARSE_IRQS=y).
And still the handler is not called even when I set qemu_set_irq.

ADD

This is the fdt generation part for the device.

static void create_ab21q_axpu_device(const Ab21qMachineState *vms)
{
    char *nodename;
    printf("create_ab21q_axpu_device called!\n");
    hwaddr base = vms->memmap[AB21Q_AXPU].base;
    hwaddr size = vms->memmap[AB21Q_AXPU].size;
    int irq = vms->irqmap[AB21Q_AXPU];  // irq = 15 (meaning SPI 15)
    const char compat[] = "ab21q-axpu";

    sysbus_create_simple("ab21q-axpu", base, qdev_get_gpio_in(vms->gic, irq));

    nodename = g_strdup_printf("/ab21q_axpu@%" PRIx64, base);
    qemu_fdt_add_subnode(vms->fdt, nodename);
    qemu_fdt_setprop(vms->fdt, nodename, "compatible", compat, sizeof(compat));
    qemu_fdt_setprop_sized_cells(vms->fdt, nodename, "reg", 2, base, 2, size);
    qemu_fdt_setprop_cells(vms->fdt, nodename, "interrupts",
                           GIC_FDT_IRQ_TYPE_SPI, irq,
                           GIC_FDT_IRQ_FLAGS_LEVEL_HI);
    qemu_fdt_setprop_cell(vms->fdt, nodename, "interrupt-parent", vms->gic_phandle);

    g_free(nodename);
}

Solution

I found how to find the irq number in my case! (char driver kernel module) I want to share it for reference to others.

Add these two header files for this.

#include <linux/irq.h>
#include <linux/irqdesc.h>

extern struct irq_desc *irq_to_desc(unsigned int irq);
struct irq_desc *desc;
unsigned int virq;

And in the module _init function :

// find my irq number
    for(i=0;i<NR_IRQS;i++){
        desc = irq_to_desc(i);
        if (desc) {
            //printk("irq_desc(%d)->irq_data.hwirq = %ld\n", i,
desc->irq_data.hwirq);
            if (desc->irq_data.hwirq == 47) break; // 47 is the hwirq number, (SPI 15)
        }
    }
    if (i == NR_IRQS) {
        printk("couldn't find irq number..\n");
        goto r_device;  // destroy device class and chrdev region..
    }
    
    virq = i;   
    ret = request_irq(virq, axpu_irq_handler, IRQF_SHARED, "axpu_irq", &axpu_cdev);

And in the module _exit funciont :

    free_irq(virq, &axpu_cdev);

This way I could find the correct irq number on ubuntu and vanilla linux.
Hope this is helpful to someone. (anyway you know the hardware connection so you can use it)



Answered By - Chan Kim
Answer Checked By - Dawn Plyler (WPSolving Volunteer)