UPGRADE YOUR BROWSER

We have detected your current browser version is not the latest one. Xilinx.com uses the latest web technologies to bring you the best online experience possible. Please upgrade to a Xilinx.com supported browser:Chrome, Firefox, Internet Explorer 11, Safari. Thank you!

cancel
Showing results for 
Search instead for 
Did you mean: 
Visitor marleco
Visitor
23,552 Views
Registered: ‎11-03-2008

problem to request an IRQ in custom linux device driver

Hello,
I'm currently writing a linux device driver for a custom peripheral on our board (with a XC5VF70T). We are using the version 2.6.26 of the OSL linux kernel.
I need to use an interrupt (connected to the xps_intc IP) for this driver but the function request_irq, called in module_init function always return -38 (ENOSYS).
It seems the setup_irq function failed.

Can anyone help me?

 

Thanks

 

Olivier

0 Kudos
13 Replies
Xilinx Employee
Xilinx Employee
23,551 Views
Registered: ‎09-10-2008

Re: problem to request an IRQ in custom linux device driver

Is the EDK project configured such that the interrupt controller should have the extra interrupt connected and the number of interrupts in the interrupt controller is correct?

 


The device tree is generated from the EDK project.  Review the device tree to make sure you believe the interrupt controller is setup correctly. 

 

I have not seen or had this experience before.

 

Thanks,

John

0 Kudos
Visitor marleco
Visitor
23,525 Views
Registered: ‎11-03-2008

Re: problem to request an IRQ in custom linux device driver

Actulally, my problem is that I don’t know exactly the relationship between the "hard number" of an interrupt and its "logical number" under linux kernel. For example, my UART console interrupt has the lowest priority in the xps_intc configurer under XPS which corresponds to the number 15 in the xilinx.dts file used by the kernel, but the logical number of this interrupt is 16 under linux.

Another example, the "xilinx_dma_rx_int" and the "xilinx_dma_tx_int" have respectively the number 2 and 3 in the xilinx.dts file and have respectively the logical number 19 and 20 under linux.

How this "translation" is it done in the kernel?

 

Thanks

 

Olivier

0 Kudos
Xilinx Employee
Xilinx Employee
23,518 Views
Registered: ‎09-10-2008

Re: problem to request an IRQ in custom linux device driver

You'd have to look in the kernel code as I've never had a reason to care.

 

As long as there are enough interrupts on the interrupt controller I'm not sure why you would care.

 

-- John

 

0 Kudos
Xilinx Employee
Xilinx Employee
23,116 Views
Registered: ‎04-23-2008

Re: problem to request an IRQ in custom linux device driver

For ARCH=powerpc your new driver must use the device tree. The value to use for request_irq() is provided with of_irq_to_resource(), which fishes out this hardware property and provides the virtual mapping that request_irq() will later expect.

 

-Brian

0 Kudos
Contributor
Contributor
22,491 Views
Registered: ‎12-06-2008

Re: problem to request an IRQ in custom linux device driver

Olivier,

 

I am also trying to get interrupts working with this linux kernel on a powerpc processor and was wondering if you could give me some general tips on what to do here, it would help me a lot since I'm unfamiliar with this problem.  I can get an interrupt controller and my interrupting peripheral setup in xps, but I'm uncertain about what to do in the linux kernel to connect these to a handler.  Could you give me some links to where you learned about this subject with the ppc, and also if you're allowed the code that you used to assign an interrupt handler (I'm sure it would be a very good example)?  If you can give me just a few links/references, it would really help a lot.  Thanks!

 

David VB

0 Kudos
Xilinx Employee
Xilinx Employee
22,440 Views
Registered: ‎04-23-2008

Re: problem to request an IRQ in custom linux device driver

> (I'm sure it would be a very good example)?  If you can give me just a few links/references, it would really help a lot.

 

I'm afraid I don't have an example to share (yet).  It is intended to release an example when EDK 11.1 is avaiable (in a month or so). And, unfortunately, the Open Firmware API seems to be poorly documented.  I mostly figured things out digging through the source code.  Not the most palatable answer, I know.

 

Look at the Xilinx XPS LL TEMAC driver: 

drivers/net/xilinx_lltemac/

 

Pay particular attention to where request_irq() is called, and, most importantly, where the number requested came from.  It was produced by of_irq_to_resource().  The number provided is a virtualized number representing the IRQ found in the device tree (i.e. arch/powerpc/boot/dts/virtex440-ml507.dts )

 

        Hard_Ethernet_MAC: xps-ll-temac@81c00000 {
            ...
            ethernet@81c00000 {
                ...
                interrupt-parent = <&xps_intc_0>;
                interrupts = < 5 2 >;

                ...

 

In other words, be sure your device is represented in the device tree.

For what little there is written about Open Firmware (and you won't find it to be enough) look to the kernel documentation here:

Documentation/powerpc/booting-without-of.txt

 

-Brian

 

 

0 Kudos
Visitor marleco
Visitor
22,435 Views
Registered: ‎11-03-2008

Re: problem to request an IRQ in custom linux device driver

David,

Find below a part of the intitialization function of a custom driver:

 ...

static int     irq;

static test_hw_map  *module_register;
static struct cdev   test_cdev;

...

 

// IRQ handler

 static irqreturn_t test_irq_handler(int irq, void *dev_id)

{

      ....

 

      return IRQ_HANDLED;

}

 

int test_init(void)
{
   int     result;
   struct device_node *np=NULL;
   struct resource  resource;
   unsigned long  *virt_addr;
   unsigned int  startAddr;

 

   // get the device information from its "device tree" name

   np = of_find_node_by_name(NULL, "test");
   if (!np)
   {
      printk(KERN_ERR "test: can't find compatible node\n");
      return -ENODEV;
   }
   else
   {


      result = of_address_to_resource(np, 0, &resource);
      if (result < 0)
         return result;

      printk(KERN_INFO "test: reg. size=%d Bytes\n", (u32)resource.end-(u32)resource.start);
      startAddr = (unsigned int)resource.start;

  

      // get a virtual irq number from device resource

      irq = of_irq_to_resource(np, 0, &resource);
      if (irq == NO_IRQ)
      {
         printk(KERN_ERR "test: of_irq_to_resource() failed\n");
         of_node_put(np);
         return -ENODEV;
      }
      printk(KERN_INFO "test: irq=%d\n", irq);

      // mask corresponding interrupts
      mask_interrupts(irq);


  // Make sure the locks are initialized
  spin_lock_init(&test_lock);

  cdev_init(&test_cdev, &test_fops);

  if (cdev_add(&test_cdev, MKDEV(MAJOR_NUM, 0), 1)  || (register_chrdev_region(MKDEV(MAJOR_NUM, 0), 1, DEVICE_NAME)) < 0)
  {
    printk(KERN_ERR "test: cannot register /dev/test\n");
    return -ENODEV;
  }

  // map the physical address into the virtual memory space
  virt_addr = of_iomap(np, 0);

  module_register = (test_hw_map*)virt_addr;
  printk(KERN_INFO "test at 0x%08X mapped to 0x%08X\n", startAddr, (u32)module_register);

 

  // install the IRQ handler
  result = request_irq(irq, test_irq_handler, 0, "test", NULL);
  if (result < 0)
  {
      printk(KERN_ERR "enable to request IRQ%d : %d\n", irq, result);
      of_node_put(np);
      return -ENODEV;
  }
 }
 printk(KERN_INFO "Test module inserted successfully\n");

 return 0;
}

 

I hope that help you, this source code is certainly not perfect but it works.

Best Regards

 

Olivier

0 Kudos
Contributor
Contributor
22,272 Views
Registered: ‎12-06-2008

Re: problem to request an IRQ in custom linux device driver

I am struggling with finding which files to edit in the linux source tree for this.  My first task is really just to get a hello world type driver working.  Where can I learn how to add my own device files to the kernel?  There seem to be plenty of tutorials and books on adding modules to a kernel one is running on, but few on how to get one in there before build.

 

I've been trying to edit Kconfig files and all the recursively called Makefiles to try to get my own code in there but so far not much progress.  Are there sources for this, or can someone give me a least a list of what files I'll be working with (big picture)?  One particular file I'm unsure about is built-in.o in some example folders...

 

If you can answer any of these questions or provide further pointers it is very appreciated.

 

David VB

0 Kudos
Xilinx Employee
Xilinx Employee
22,253 Views
Registered: ‎04-23-2008

Re: problem to request an IRQ in custom linux device driver

David,

Yes, you would edit the applicable Kconfig and Makefile in the directory you are using. and then do 'make menuconfig' to enable your addition.

 

For example, if you are writing a new characterdriver, you could add this to <kernel>/drivers/char/Kconfig

 

config MY_TEST_DRIVER

    bool "My Test Driver"

 

Then add your config to the <kernel>/drivers/char/Makefile

obj-$(MY_TEST_DRIVER) +=  myfile.o

(where you have a myfile.c in this directory)

 

-Brian

 

0 Kudos
Contributor
Contributor
8,892 Views
Registered: ‎12-06-2008

Re: problem to request an IRQ in custom linux device driver

I am getting close to having interrupts working but there are a few details I apparently don't understand.  My driver code is below; it depends a lot on what Olivier did, since I didn't find many sources on of_xxx stuff except for in the kernel source (prom.h).  I was wondering where people learned about this subject - booting-without-of.txt didn't have anything on it from what I saw.

 

Currently this driver is properly inserted into the kernel:

...

io scheduler cfq registered (default)
timer_intr init
my-timer: reg. size=65535 Bytes
my-timer: irq=16
Timer with interrupt module inserted successfully
Serial: 8250/16550 driver4 ports, IRQ sharing disabled

...

 

... but when I try to run a program that generates an interrupt I don't get the printk message from the interrupt handler (I have a timer that counts down and then generates an interrupt when it hits zero).  I am pretty darn sure that it is correctly generating an interrupt b/c I am watching registers as the c program runs and see them change when the interrupt is applied.  I look at /proc/interrupts and the appropriate line in proc/stat and also see nothing up:

 

# cat /proc/interrupts
           CPU0
 16:          0   Xilinx INTC Edge      my-timer
 17:       1606   Xilinx INTC Edge      serial
 18:      74252   Xilinx INTC Edge      systemace
BAD:          0
# cat /proc/stat
cpu  59 0 669 31568 4 195 96 0 0
cpu0 59 0 669 31568 4 195 96 0 0
intr 75948 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1696 74252 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 ...

 

If Olivier is still watching this post, it would be nice to know what the mask_interrupts(irq) function does - is that something you wrote that changes the cpu mask to not ignore interrupt 16?

 

In short, it looks like there is a disconnect between interrupt 16 and the physical interrupt.  Anyone know what may be going on here?

 

David VB

 

note - if anyone else discovers this post and is wondering about the makefiles/kconfigs in the kernel I discovered some more help from Embedded Linux Primer by Hallinan.  Also Linux Device Drivers by Corbet has helped with driver writing.

 

---------------------------------------------------------------------------------------------------------------------------------------------

 

/* 
 *  timer_intr.c - Connecting a hardware interrupt to the simple timer peripheral.
 */
#include <linux/module.h>    /* Needed by all linux kernel driver modules */
#include <linux/kernel.h>    /* Needed for KERN_INFO */
#include <linux/cdev.h>        /* provides cdev struct, how the kernel represents char devices internally */
...

// #ifdef CONFIG_OF
// For open firmware.
#include <linux/of_device.h>
#include <linux/of_platform.h>
// #endif

#define MAJOR_NUM 234
#define DEVICE_NAME "my-timer"

static int irq;  // the virtual irq number that will be requested from open firmware
// static test_hw_map *module_register;
static struct cdev timer_intr_cdev;
spinlock_t timer_intr_lock = SPIN_LOCK_UNLOCKED;

/* IRQ HANDLER */
static irqreturn_t timer_intr_irq_handler(int irq, void *dev_id) {
    printk(KERN_ERR "printk err timer_intr - in the irq handler!\n");
    pr_err("pr_err timer_intr - in the irq handler!\n");
    return IRQ_HANDLED;
}

/* FILE SYSTEM OPERATIONS */
static int timer_intr_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "timer_intr_open: successful\n");
    return 0;
}
struct file_operations timer_intr_fops = {
    .owner =    THIS_MODULE,
    .open  =    timer_intr_open,
};

/* LOADING/UNLOADING DRIVER FUNCTIONS */
/* init function is run when driver is added to kernel */
static int __init timer_intr_init(void) {
    struct device_node *np = NULL;
    int result;
    struct resource resource;
    unsigned long *virt_addr;
    unsigned int startAddr;

    // check how to get output
    printk(KERN_INFO "timer_intr init\n");
   
    np = of_find_node_by_name(NULL, "my-timer");
    if (!np) {
        printk(KERN_ERR "my-timer: can't find compatible node in this kernel build");
        return -ENODEV;
    } else {
        result = of_address_to_resource(np, 0, &resource);
        if (result < 0) {
            return result;
        }
        printk(KERN_INFO "my-timer: reg. size=%d Bytes\n", (u32)resource.end - (u32)resource.start);
        startAddr = (unsigned int)resource.start;
       
        // get a virtual irq number from device resource struct
        irq = of_irq_to_resource(np, 0, &resource);
        if (irq == NO_IRQ) {
            printk(KERN_ERR "my-timer: of_irq_to_resource failed...\n");
            of_node_put(np);
            return -ENODEV;
        }
        printk(KERN_INFO "my-timer: irq=%d\n", irq);
        // mask corresponding interrupts
        // mask_interrupts(irq);                    // FIXME!
       
        // make sure our lock is initialized - though this should have happened at compile time.
        // looks like this spin_lock is never used anyway...
        spin_lock_init(&timer_intr_lock);

        cdev_init(&timer_intr_cdev, &timer_intr_fops);
        if (cdev_add(&timer_intr_cdev, MKDEV(MAJOR_NUM, 0), 1) ||
            (register_chrdev_region(MKDEV(MAJOR_NUM, 0), 1, "my-timer")) < 0) {
            printk(KERN_ERR "my-timer: cannot register /dev/my-timer\n");
            return -ENODEV;
        }
        // map the physical address into the virtual address space
        virt_addr = of_iomap(np, 0);
       
        // install the irq handler
        result = request_irq(irq, timer_intr_irq_handler, 0, "my-timer", NULL);
        if (result < 0) {
            printk(KERN_ERR "unable to request IRQ%d : %d\n", irq, result);
            of_node_put(np);
            return -ENODEV;
        }
    }
    printk(KERN_INFO "Timer with interrupt module inserted successfully\n");
   
    return 0;
}

static void __exit timer_intr_exit(void)
{
    printk(KERN_INFO "Goodbye - timer_intr\n");   
}

module_init(timer_intr_init);
// subsys_initcall(timer_intr_init);
module_exit(timer_intr_exit);
0 Kudos
Visitor marleco
Visitor
8,882 Views
Registered: ‎11-03-2008

Re: problem to request an IRQ in custom linux device driver

David,

The "mask_interrupts()" function I implemented in the init function is described below. It mask the corresponding interrupt, which means that the interrupt is disable after the load of the driver. In my code, I choose to enable the interrupt (unmask_interrupts()) only in the "open" function.

For your problem, are sure that your interrupt is correctly connected to the interrupt controler in your XPS project, is your dts file matching with the firmware?

 

Best Regards

 

Olivier

 

// INTC Registers
typedef struct
{
 unsigned int  intc_isr; // Interrupt Status
 unsigned int  intc_ipr; // Interrupt Pending
 unsigned int  intc_ier; // Interrupt Enable
 unsigned int  intc_iar; // Interrupt Acknowledge
 unsigned int  intc_sie; // Set Interrupt Enable bits
 unsigned int  intc_cie; // Clear Interrupt Enable bits
 unsigned int  intc_ivr; // Interrupt Vector
 unsigned int  intc_mer; // Master Enable
} intc_hw_map;

 

 

static void mask_interrupts(unsigned int virq)
{
 int hw_irq = virq_to_hw(virq);


 intc_register->intc_cie |= 1 << hw_irq;
 //printk(KERN_INFO "mask irq %d - addr=0x%08X\n", hw_irq, (u32)regs);
}

 

static void unmask_interrupts(unsigned int virq)
{
 int hw_irq = virq_to_hw(virq);
 intc_register->intc_sie |= 1 << hw_irq;
}

 

0 Kudos
Contributor
Contributor
8,858 Views
Registered: ‎12-06-2008

Re: problem to request an IRQ in custom linux device driver

OK, I see what you are doing.  I was not even messing with xilinx's interrupt controller before this; I thought that was dealt with by the kernel, but apparently not.  Do you have to map all of the interrupt controller to virtual address space before you can do anything with it?  That's what I've been attempting...

 

My whole .dts is below; it looks like everything is correctly attached from what I know.  I also have different code and the output.  I'm getting lost on what is going on here, it seems there are multiple problems...

 

Thanks very much,

David VB

 

 

/*
 * Device Tree Generator version: 1.1
 *
 * (C) Copyright 2007-2008 Xilinx, Inc.
 * (C) Copyright 2007-2008 Michal Simek
 *
 * Michal SIMEK <monstr@monstr.eu>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 *
 * CAUTION: This file is automatically generated by libgen.
 * Version: Xilinx EDK 10.1.03 EDK_K_SP3.6
 *
 * XPS project directory: RFS_Periph_Interrupts_2
 */

/dts-v1/;
/ {
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "xlnx,virtex405", "xlnx,virtex";
    model = "testing";
    DDR_SDRAM: memory@0 {
        device_type = "memory";
        reg = < 0x0 0x10000000 >;
    } ;
    chosen {
        bootargs = "console=ttyS0 root=/dev/xsa2 rw";
        linux,stdout-path = "/plb@0/serial@83e00000";
    } ;
    cpus {
        #address-cells = <1>;
        #cpus = <0x1>;
        #size-cells = <0>;
        ppc405_0: cpu@0 {
            clock-frequency = <100000000>;
            compatible = "PowerPC,405", "ibm,ppc405";
            d-cache-line-size = <0x20>;
            d-cache-size = <0x4000>;
            dcr-access-method = "native";
            dcr-controller ;
            device_type = "cpu";
            i-cache-line-size = <0x20>;
            i-cache-size = <0x4000>;
            model = "PowerPC,405";
            reg = <0>;
            timebase-frequency = <100000000>;
            xlnx,dcr-resync = <0x0>;
            xlnx,deterministic-mult = <0x0>;
            xlnx,disable-operand-forwarding = <0x1>;
            xlnx,fastest-plb-clock = "DPLB0";
            xlnx,generate-plb-timespecs = <0x1>;
            xlnx,mmu-enable = <0x1>;
        } ;
    } ;
    plb0: plb@0 {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "xlnx,plb-v46-1.03.a", "simple-bus";
        ranges ;
        DDR_SDRAM: mpmc@84800000 {
            #address-cells = <1>;
            #size-cells = <1>;
            compatible = "xlnx,mpmc-4.03.a";
            reg = < 0x84800000 0x10000 >;
        } ;
        DIPSWs_4Bit: gpio@81440000 {
            compatible = "xlnx,xps-gpio-1.00.a";
            interrupt-parent = <&xps_intc_0>;
            interrupts = < 2 2 >;
            reg = < 0x81440000 0x10000 >;
            xlnx,all-inputs = <0x1>;
            xlnx,all-inputs-2 = <0x0>;
            xlnx,dout-default = <0x0>;
            xlnx,dout-default-2 = <0x0>;
            xlnx,family = "virtex2p";
            xlnx,gpio-width = <0x4>;
            xlnx,interrupt-present = <0x1>;
            xlnx,is-bidir = <0x1>;
            xlnx,is-bidir-2 = <0x1>;
            xlnx,is-dual = <0x0>;
            xlnx,tri-default = <0xffffffff>;
            xlnx,tri-default-2 = <0xffffffff>;
        } ;
        LEDs_4Bit: gpio@81420000 {
            compatible = "xlnx,xps-gpio-1.00.a";
            interrupt-parent = <&xps_intc_0>;
            interrupts = < 3 2 >;
            reg = < 0x81420000 0x10000 >;
            xlnx,all-inputs = <0x0>;
            xlnx,all-inputs-2 = <0x0>;
            xlnx,dout-default = <0x0>;
            xlnx,dout-default-2 = <0x0>;
            xlnx,family = "virtex2p";
            xlnx,gpio-width = <0x4>;
            xlnx,interrupt-present = <0x1>;
            xlnx,is-bidir = <0x0>;
            xlnx,is-bidir-2 = <0x1>;
            xlnx,is-dual = <0x0>;
            xlnx,tri-default = <0xffffffff>;
            xlnx,tri-default-2 = <0xffffffff>;
        } ;
        PushButtons_5Bit: gpio@81400000 {
            compatible = "xlnx,xps-gpio-1.00.a";
            interrupt-parent = <&xps_intc_0>;
            interrupts = < 1 2 >;
            reg = < 0x81400000 0x10000 >;
            xlnx,all-inputs = <0x1>;
            xlnx,all-inputs-2 = <0x0>;
            xlnx,dout-default = <0x0>;
            xlnx,dout-default-2 = <0x0>;
            xlnx,family = "virtex2p";
            xlnx,gpio-width = <0x5>;
            xlnx,interrupt-present = <0x1>;
            xlnx,is-bidir = <0x1>;
            xlnx,is-bidir-2 = <0x1>;
            xlnx,is-dual = <0x0>;
            xlnx,tri-default = <0xffffffff>;
            xlnx,tri-default-2 = <0xffffffff>;
        } ;
        RS232_Uart_1: serial@83e00000 {
            clock-frequency = <100000000>;
            compatible = "xlnx,xps-uart16550-2.00.b", "ns16550";
            current-speed = <9600>;
            device_type = "serial";
            interrupt-parent = <&xps_intc_0>;
            interrupts = < 5 2 >;
            reg = < 0x83e00000 0x10000 >;
            reg-offset = <0x1003>;
            reg-shift = <2>;
            xlnx,family = "virtex2p";
            xlnx,has-external-rclk = <0x0>;
            xlnx,has-external-xin = <0x0>;
            xlnx,is-a-16550 = <0x1>;
        } ;
        SysACE_CompactFlash: sysace@83600000 {
            compatible = "xlnx,xps-sysace-1.00.a";
            interrupt-parent = <&xps_intc_0>;
            interrupts = < 4 2 >;
            reg = < 0x83600000 0x10000 >;
            xlnx,family = "virtex2p";
            xlnx,mem-width = <0x10>;
        } ;
        my_timer_0: my-timer@c0a00000 {
            compatible = "xlnx,my-timer-1.00.a";
            interrupt-parent = <&xps_intc_0>;
            interrupts = < 0 2 >;
            reg = < 0xc0a00000 0x10000 >;
            xlnx,family = "virtex2p";
            xlnx,include-dphase-timer = <0x0>;
        } ;
        xps_bram_if_cntlr_1: xps-bram-if-cntlr@ffff0000 {
            compatible = "xlnx,xps-bram-if-cntlr-1.00.a";
            reg = < 0xffff0000 0x10000 >;
            xlnx,family = "virtex2p";
        } ;
        xps_intc_0: interrupt-controller@81800000 {
            #interrupt-cells = <0x2>;
            compatible = "xlnx,xps-intc-1.00.a";
            interrupt-controller ;
            reg = < 0x81800000 0x10000 >;
            xlnx,num-intr-inputs = <0x6>;
        } ;
    } ;
    ppc405_0_dplb1: plb@1 {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "xlnx,plb-v46-1.03.a", "simple-bus";
        ranges ;
    } ;
}  ;


----------------------------------------------------------------------------

 

/* 
 *  timer_intr.c - Connecting a hardware interrupt to the simple timer peripheral.
 */
#include <linux/module.h>    /* Needed by all linux kernel driver modules */
#include <linux/kernel.h>    /* Needed for KERN_INFO */
#include <linux/cdev.h>        /* provides cdev struct, how the kernel represents char devices internally */
#include <linux/spinlock.h>    /* provides the sprinlock type, and functions. */

// just sorta added:
#include <linux/init.h>
#include <linux/mm.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/mii.h>
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/xilinx_devices.h>
#include <asm/io.h>
#include <linux/ethtool.h>
#include <linux/vmalloc.h>
#include <asm/prom.h>

// #ifdef CONFIG_OF
// For open firmware.
#include <linux/of_device.h>
#include <linux/of_platform.h>
// #endif

#define MAJOR_NUM 234
#define DEVICE_NAME "my-timer"

// INTC Registers
typedef struct {
    unsigned int  intc_isr; // Interrupt Status
    unsigned int  intc_ipr; // Interrupt Pending
    unsigned int  intc_ier; // Interrupt Enable
    unsigned int  intc_iar; // Interrupt Acknowledge
    unsigned int  intc_sie; // Set Interrupt Enable bits
    unsigned int  intc_cie; // Clear Interrupt Enable bits
    unsigned int  intc_ivr; // Interrupt Vector
    unsigned int  intc_mer; // Master Enable
} intc_hw_map;

static int irq;  // the virtual irq number that will be requested from open firmware
// static test_hw_map *module_register;
static struct cdev timer_intr_cdev;
static intc_hw_map *intc_registers;
spinlock_t timer_intr_lock = SPIN_LOCK_UNLOCKED;

/* IRQ HANDLER */
static irqreturn_t timer_intr_irq_handler(int irq, void *dev_id) {
    printk(KERN_ERR "printk err timer_intr - in the irq handler!\n");
    pr_err("pr_err timer_intr - in the irq handler!\n");
    return IRQ_HANDLED;
}

/* FILE SYSTEM OPERATIONS */
static int timer_intr_open(struct inode *inode, struct file *file) {
    printk(KERN_INFO "timer_intr_open: successful\n");
    return 0;
}
struct file_operations timer_intr_fops = {
    .owner =    THIS_MODULE,
    .open  =    timer_intr_open,
};

/* LOADING/UNLOADING DRIVER FUNCTIONS */
/* init function is run when driver is added to kernel */
static int __init timer_intr_init(void) {
    struct device_node *np = NULL;
    int result, hw_irq;
    struct resource resource;
    unsigned long *virt_addr;
    unsigned int startAddr;

    // check how to get output
    printk(KERN_INFO "timer_intr init\n");
   
    np = of_find_node_by_name(NULL, "my-timer");
    if (!np) {
        printk(KERN_ERR "my-timer: can't find compatible node in this kernel build");
        return -ENODEV;
    } else {
        result = of_address_to_resource(np, 0, &resource);
        if (result < 0) {
            return result;
        }
        printk(KERN_INFO "my-timer: reg. size=%d Bytes\n", (u32)resource.end - (u32)resource.start);
        startAddr = (unsigned int)resource.start;
       
        // get a virtual irq number from device resource struct
        irq = of_irq_to_resource(np, 0, &resource);
        if (irq == NO_IRQ) {
            printk(KERN_ERR "my-timer: of_irq_to_resource failed...\n");
            of_node_put(np);
            return -ENODEV;
        }
        printk(KERN_INFO "my-timer: virq=%d\n", irq);
        // check the hw irq is correct
        hw_irq = virq_to_hw(irq);
        printk(KERN_INFO "my-timer: hw_irq=%d\n", hw_irq);
       
        // make sure our lock is initialized - though this should have happened at compile time.
        // looks like this spin_lock is never used anyway...
        spin_lock_init(&timer_intr_

lock);
        cdev_init(&timer_intr_cdev, &timer_intr_fops);
        if (cdev_add(&timer_intr_cdev, MKDEV(MAJOR_NUM, 0), 1) ||
            (register_chrdev_region(MKDEV(MAJOR_NUM, 0), 1, "my-timer")) < 0) {
            printk(KERN_ERR "my-timer: cannot register /dev/my-timer\n");
            return -ENODEV;
        }
        // map the physical address of the timer into the virtual address space
        virt_addr = of_iomap(np, 0);
        printk(KERN_INFO "timer at 0x%08X mapped to 0x%08X\n", startAddr, (u32)virt_addr);
       
        // install the irq handler
        result = request_irq(irq, timer_intr_irq_handler, 0, "my-timer", NULL);
        if (result < 0) {
            printk(KERN_ERR "unable to request IRQ%d : %d\n", irq, result);
            of_node_put(np);
            return -ENODEV;
        }
    }
   
    np = of_find_node_by_name(NULL, "xps-bram-if-cntlr");
    if (!np) {
        printk(KERN_ERR "xps-bram-if-cntlr: can't find compatible node in this kernel build");
        return -ENODEV;
    } else {
        result = of_address_to_resource(np, 0, &resource);
        if (result < 0) {
            return result;
        }
        printk(KERN_INFO "xps-bram-if-cntlr: reg. size=%d Bytes\n", (u32)resource.end - (u32)resource.start);
        startAddr = (unsigned int)resource.start;
       
        // map the physical address of the interrupt controller into the virtual address space
        virt_addr = of_iomap(np, 0);
        intc_registers = (intc_hw_map*) virt_addr;
        printk(KERN_INFO "interrupt controller at 0x%08X mapped to 0x%08X\n", startAddr, (u32)virt_addr);
        printk(KERN_INFO "interrupt status register = 0x%08X\n", (u32)(intc_registers->intc_isr));
        printk(KERN_INFO "interrupt enable register = 0x%08X\n", (u32)(intc_registers->intc_ier));
        printk(KERN_INFO "interrupt vector register = 0x%08X\n", (u32)(intc_registers->intc_ivr));
        printk(KERN_INFO "set interrupt enable register = 0x%08X\n", (u32)(intc_registers->intc_sie));
        intc_registers->intc_sie |= (1 << hw_irq);
        printk(KERN_INFO "interrupt status register = 0x%08X\n", (u32)(intc_registers->intc_isr));
        printk(KERN_INFO "interrupt enable register = 0x%08X\n", (u32)(intc_registers->intc_ier));
        printk(KERN_INFO "interrupt vector register = 0x%08X\n", (u32)(intc_registers->intc_ivr));
        printk(KERN_INFO "set interrupt enable register = 0x%08X\n", (u32)(intc_registers->intc_sie));
    }
   
    printk(KERN_INFO "Timer with interrupt module inserted successfully\n");
   
    return 0;
}

static void __exit timer_intr_exit(void)
{
    printk(KERN_INFO "Goodbye - timer_intr\n");   
}

module_init(timer_intr_init);
// subsys_initcall(timer_intr_init);
module_exit(timer_intr_exit);

MODULE_AUTHOR("David VandeBunte");
MODULE_LICENSE("GPL");
 
 
 --------------------------------------------
output:
timer_intr init
my-timer: reg. size=65535 Bytes
my-timer: virq=16
my-timer: hw_irq=0
timer at 0xC0A00000 mapped to 0xD1060000         // note that this is correct hw address from xps
xps-bram-if-cntlr: reg. size=65535 Bytes
interrupt controller at 0xFFFF0000 mapped to 0xD1080000       // note that this is correct hw address from xps
interrupt status register = 0x00000000
interrupt enable register = 0x00000000
interrupt vector register = 0x00000000
set interrupt enable register = 0x00000000
interrupt status register = 0x00000000
interrupt enable register = 0x00000000
interrupt vector register = 0x00000000
set interrupt enable register = 0x00000001
Timer with interrupt module inserted successfully

 

0 Kudos
Observer mincher
Observer
8,407 Views
Registered: ‎01-19-2009

Re: problem to request an IRQ in custom linux device driver

This code would work fine if you are a GPL'd module.  The function of_irq_to_resource, for example, is exported with EXPORT_SYMBOL_GPL which is technically violating the GPL if you attempt to use it without your module being declared as GPL (and you being required to distribute source code).

 

Is there a simple "cheat" to figure out what the virtual mapping is based on the interrupt property of the device node?

 

-D i c k

0 Kudos