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 functionrequest_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 queueUsing 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);}