Introduction to GIC Interrupts

GIC controller leads out

  • First of all, each processor has its own interrupt source. From the above figure, you can see PPIs and FIQ and IRQ, and FIQ and IRQ are directly connected to the processor without going through the distributor.
    PPIs (Private Peripheral Interrupt) are private peripheral interrupts, which are connected to peripherals. Each processor core has its own private peripheral interrupt; the interrupt number is 16~31.
    SPIs (Shared Peripheral Interrupt) are shared peripheral interrupts, which share the interrupt number and are visible to each processor; the interrupt number is 32~1019.
  • As can be seen from the above figure, all external and internal interrupts are managed by GIC, and each core has its own FIQ and IRQ and PPIs,
    But all cores share SPI interrupt
  • As the complexity of the processor increases, one GIC becomes insufficient, and multiple GICs appear. In order to facilitate management, the GIC Domain method appears
    (Interrupt number + domain) For better upper management, a virtual interrupt number (soft interrupt IRQ and hardware interrupt hw IRQ) is introduced. The default IRQ is the soft interrupt number, and the interrupt number checked through the datasheet is the hardware interrupt number. And software and hardware need to map to find each other
GIC controller driver

(INTC supports limited interrupt numbers and cannot take care of interrupt line problems between multi-core processors)

  • At present, the Cortex-A7 series all use the GIC interrupt controller, and the linux kernel implements a complete set of GIC controller driver source code for it. We only need to add the GIC interrupt controller node in the device tree. For example, our datasheet tells us that this controller uses the GIC-400 interrupt controller, which is a general driver provided by the linux kernel.
    gic: interrupt-controller@1c81000 {
    compatible = "arm,gic-400";//Specify the interrupt controller driver that needs to be matched
    reg = <0x01c81000 0x1000>,//The registers of this controller that distribute the configuration GICD
    <0x01c82000 0x2000>,//CPU interface GICIF
    <0x01c84000 0x2000>,//Access the virtual interface of the CPU
    <0x01c86000 0x2000>;//Address bit selection processing
    interrupt-controller;//Indicates that this node is an interrupt controller
    #interrupt-cells = <3>;
    interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) | IRQ_TYPE_LEVEL_HIGH)>;//Interrupt number PPI 9 and trigger status
    };
    
  • In the interrupt controller frame diagram, we can see that there are up to 1020 interrupts. If all these interrupt numbers are allocated a box of virtual interrupt numbers in advance, it will take up a lot of space. In the actual development process, there are more than a dozen interrupt numbers used. There are already many, so the Linux kernel does not allocate all the connections between virtual and hardware interrupts when initializing the mid-segment mapping. Only the interrupts defined in the device tree dtb file will be mapped. So the interrupt node must be defined in the device tree before using the interrupt, otherwise the interrupt cannot be registered
  • Add controller node
    intc: interrupt-controller@1c20400 {
    compatible = "xxx, xxx-inct";
    reg = <0x01c20400 0x400>;
    interrupt-controller;
    #interrupt-cells = <1>;//The byte point and grandchild node of the interrupt controller have only one cell
    };
    
  • break registration
    The linux kernel provides interrupt interface functions, including interrupt application, release, disable, enable
    1* Interrupt request function
    request_irq(62, btn_irq, IRQF_TRIGGER_FALLING, "btn",(void*)&key_value);
    	request_irq(unsigned int irq,//Hardware Interrupt Number, Data Sheet
    	irq_handler_t handler,//interrupt handler
    	unsigned long flags,//interrupt trigger flag
    	const char* name,//interrupt name
    	void* dev)//Device ID, as long as it is unique
    
    //For threaded interrupts, it is split into two parts (the upper and lower halves)
    request_threaded_irq(	unsigned int irq, 
    	irq_handler_t handler,//Triggered interrupt service function (top half)
    	irq_handler_t
    	thread_fn,//the bottom half of the break
    	unsigned long flags, 
    	const char *name, void *dev);
    

  • interrupt release function
    extern const void *free_irq(unsigned int,//interrupt number
    void *);//Interrupt device

  • interrupt enable/disable
    Requirement: Turn off interrupts for a certain period of time, then turn on interrupts. The linux kernel provides API s
    extern void disable_irq(unsigned int irq);//Close a single
    extern void enable_irq(unsigned int irq);

  • Turn off global interrupts
    #define local_irq_enable() do { raw_local_irq_enable(); } while (0)
    #define local_irq_disable() do { raw_local_irq_disable(); } while (0)

Write an ADC button interrupt: (press button -> ADC trigger -> ADC read data -> judge button value)
  • For the application, it is to continuously read the keys (files), so that if there is no interruption in most cases, the program will always do useless work.

  • At this point, we can read the key first, and the driver will immediately put it to sleep, and then wake it up when an interrupt arrives. linux provides a waiting queue to store tasks that need to sleep. When there is an interrupt, it will be woken up in the interrupt handle.

  • Add a node (define the interrupt number inside)
    mykey: mykey@1c22800 {
    compatible = "mykey";
    reg = <0x01c22800 0x4>, <0x01c22804 0x4>, <0x01c22808 0x4>, <0x01c2280c 0x4>;
    interrupts = <GIC_SPI 30 IRQ_TYPE_LEVEL_HIGH>;
    status = "okay";
    };

  • A waiting queue for btn_waitq is defined, and the waiting queue is actually a linked list structure
    DECLARE_WAIT_QUEUE_HEAD(btn_waitq);

    wait_event_interruptible(wq_head, //wq_head is waiting in the queue
    condition)// is a flag, 1 is wake up, 0 is sleep
    wake_up_interruptible(x)//This function wakes up all tasks in the x waiting queue

    Using the platform framework, the purpose is that we do not have to implement the mapping relationship between the hardware interrupt number and the software interrupt number by ourselves. This process is done by the GIC driver (when parsing the dtb file, we only need to specify the interrupts attribute in the device tree)

  • In fact, the above picture also recalls the important characteristics of the platform driver. The kernel will parse the device tree and automatically generate plaform_device. At this time, we do not need to write the device file by ourselves.

- static int btn_probe(struct platform_device *pdev) {//Device registration and IO remapping are omitted here
	ret = devm_request_irq(&pdev->dev, platform_get_irq(pdev, 0), btn_irq, 0, "mykey", 
	(void*)&key_value);}
	- static irqreturn_t btn_irq(int irq, void *dev_id) {
	wake_up_interruptible(&btn_waitq); /* Wake up the sleeping process, that is, the process that calls the read function */
	ev_press = 1; *keyadc_ints |= ((1<<0)|(1<<1) | (1<<2) |(1<<3)|(1<<4));
	return IRQ_HANDLED; }
	
- static ssize_t btn_cdev_read(struct file * file, char __user * userbuf, size_t count, loff_t * off) {
	int ret;unsigned int adc_value;
	adc_value = *(keyadc_data);//Define a variable to store the LRADC data
	//Put the current process into the waiting queue button_waitq and release the CPU to sleep
	wait_event_interruptible(btn_waitq, ev_press!=0);
	ret = copy_to_user(userbuf, &adc_value, 4);//Pass the obtained key value to the upper-layer application
	printk(KERN_WARNING"key adc = %d\n",(adc_value&(0x3f)));
	printk(KERN_WARNING"key statue = 0x%x\n",*(keyadc_ints));
	ev_press = 0;//The key has been processed and can continue to sleep}
	
- static int btn_cdev_open (struct inode * inode, struct file * file) {
	//LRADC needs to be initialized, period, trigger mode, conversion delay time
	*keyadc_intc |= ((1<<0)|(1<<1) | (1<<2) |(1<<3)|(1<<4));
	*keyadc_ctrl |= (1<<0);}

Tags: Single-Chip Microcomputer Linux Driver

Posted by NorthWestSimulations on Sat, 15 Oct 2022 10:22:07 +1030