Interrupt Handling in Linux

From RCSWiki

Revision as of 19:03, 20 August 2009 by Bhuang2 (Talk | contribs)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search

Contents

About

  • Author: Bin Huang
  • Last Modified: 08/20/2009
  • Notes1: This tutorial introduces a simple way to hook up interrupt in reader's customized core and Linux device driver. It assumes the reader has created a hardware core with its interrupt pin connecting to Xilinx interrupt controller.
  • Notes2: If the reader just get started with FPGA/Linux mixed design, it is recommend to refer to Andy's tutorial Linux 2.6 on ML-410 to learn how to create a base system, and Sid's tutorial Adding / Using Interrupts Tutorial to learn how to connect reader's interrupt to Xilinx interrupt controller.

Setting Hardware Platform in system.mhs

  1. It assumes reader's core is named: test_core_v1_00_a
  2. In system.mhs file, it also assumes that reader has defined one (or more) interrupt(s) named Test_Core_Interrupt ported from test_core_v1_00_a.
  3. Make sure Test_Core_Interrupt is connected to Xilinx interrupt controller.

The bottom of your xps_intc should look like this

 BEGIN xps_intc
  PARAMETER INSTANCE = xps_intc_0
  PARAMETER HW_VER = 1.00.a
  PARAMETER C_BASEADDR = 0x81800000
  PARAMETER C_HIGHADDR = 0x8180ffff
  BUS_INTERFACE SPLB = plb
  PORT Irq = EICC405EXTINPUTIRQ
  PORT Intr = RS232_Uart_1_Interrupt & Test_Core_Interrupt
 END

Setting Software Platform

  1. After generating the Hardware Base System you need to configure the Software Platform to change the project from a Standalone C project to a Linux 2.6 project.
    1. From the Software Menu click Generate Libraries and BSPs, it should create xparameters_ml40x.h which will be located at: <build directory>/ppc405_0/libsrc/linux_2_6_v1_00_c/linux/arch/ppc/platform/4xx/xparameters/xparameters_ml40x.h
  2. In this xparameters_ml40x.h, definitions for reader's core should look like this:
 /* Definitions for peripheral TEST_CORE_0 */
 #define XPAR_TEST_CORE_0_BASEADDR 0xCE400000
 #define XPAR_TEST_CORE_0_HIGHADDR 0xCE40FFFF
 
 /* Definitions for peripheral XPS_INTC_0 */
 #define XPAR_TEST_CORE_0_MASK 0X000001
 #define XPAR_XPS_INTC_0_TEST_CORE_0__INTR 0
 #define XPAR_RS232_UART_1_INTERRUPT_MASK 0X000002
 #define XPAR_XPS_INTC_0_RS232_UART_1_INTERRUPT_INTR 1

Here, <CORE_NAME>_MASK will be later used by Xilinx interrupt controller driver in arch/ppc/syslib/xilinx_pic.c. The reader will need to call <CORE_NAME>_INTR in his/her device driver so that Linux can assign correct IRQ number to device driver. When an interrupt event comes to powerpc405, correct interrupt line can be chosen only if IRQ number in device driver matches that in hardware.

Adding kernel subroutines to device driver

  1. Generally speaking, a kernel module is expected to request an interrupt channel before using it, and to release it when it is done. For our simple example, two subroutines need to be implemented:
    • Request an interrupt channel with request_irq()
      • The correct place to call request_irq() is when the device is first opened before the hardware is instructed to generate interrupts.
      • After successfully requesting an interrupt, the reader may need to disable interrupt to avoid interrupt racing.
    • Release an interrupt channel with free_irq()
      • The place to call free_irq() is the last time the device is closed, after the device is told not to interrupt the processor any more.
  2. Implementation of these two functions has been fully discussed in <Linux device driver 3> chapter 10. The point here is passing our XPAR_XPS_INTC_0_TEST_CORE_0_INTR to device driver.

Example: A template of char device driver for test_core

Here is a simple template of char device driver for test_core:

 /* Included Headers */
 #include <linux/module.h>
 #include <linux/ioport.h>
 #include <linux/slab.h>
 #include <linux/types.h>
 #include <linux/fs.h>
 #include <linux/errno.h>
 #include <asm/io.h>
 #include <asm/uaccess.h>
 #include <asm/delay.h>
 
 /* Need to include xparameters.h for test_core device address */
 #include <platforms/4xx/xparameters/xparameters.h>
 
 /* Included Headers for IRQ */
 #include <linux/interrupt.h>
 
 /* License for GPL */
 MODULE_LICENSE("GPL") ;
 
 irqreturn_t test_core_handler(int irq, void *dev_id){
     printk(KERN_INFO "You have received interrupt from test_core \n");
     return IRQ_HANDLED;
 }
 
 /* Test_Core File Operations */
 struct file_operations test_core_fops = {
   .owner        = THIS_MODULE,
 };
 
 /* Test_Core Device Structure */
 static struct test_core0_s {
   int base;
   unsigned long len;
 } first;
 static struct test_core0_s *test_core = &first;
 
 static int test_core_init(void) {
   int result = 0;
   //* Base Address & Length of the TEST_CORE Hardware Core */
   test_core->base = XPAR_TEST_CORE_0_BASEADDR;
   test_core->len  = (XPAR_TEST_CORE_0_HIGHADDR + 1) - XPAR_TEST_CORE_0_BASEADDR;
   
   /* Check if Memory Region has already been alocated */
   request_mem_region(test_core->base,test_core->len,"test_core") ;
   
   /* Initialize Interrupt channel */
   result = request_irq(XPAR_XPS_INTC_0_TEST_CORE_0_INTR, test_core_handler,0, "test_core_test", (void *) 0);
   
   register_chrdev(241,"test_core",&test_core_fops) ;
 }
 
 static void test_core_exit(void) {
     /* Unregister TEST_CORE Device */
    unregister_chrdev(241,"test_core") ;
 
    /* Release Interrupt channel */
    free_irq(XPAR_XPS_INTC_0_TEST_CORE_0_INTR, (void *)0);
  
    /* Release Memory Region */
    release_mem_region(test_core->base,test_core->len) ;
    printk(KERN_ALERT "TEST_CORE Driver Removed\n") ;
 }
 
 module_init(test_core_init);
 module_exit(test_core_exit);
  1. Build your kernel module under Linux as normal. It assumes you have a kernel module named "test_core.ko"
  2. You can test it with the insmod at command line . You should be able to trigger interrupt handler and see "You have received interrupt from test_core" on screen.
 root#insmod ./test_core.ko
 You have received interrupt from test_core
  1. For simplicity, our test program requests its interrupt at load time without having to run an extra process to keep the device open. In a real device driver, it's recommended to do it in test_core_open rather than test_core_init. In that case, a module will not prevent any other driver from using the interrupt while it holds but never uses an interrupt.

Final word

This technique is not limited to char type device driver. It can be applied to other types of device driver such as network device driver. Refer to counterpart in Linux device driver Edition 3 if you are interested.

Reference

Linux device driver Edition 3 Chapter 10

Personal tools